Details
Description
Unable to mock groovyx.net.http.HTTPBuilder using mockFor() and createMock(). After invoking createMock() a URISyntaxException is thrown.
It appears that at line 90 and 91 of GrailsMock it is calling asType(HTTPBuilder) on an empty map. Because asType is being invoked on a Map, DefaultTypeTransformation.castToType(Object, Class) chooses to invoke a constructor with a single Map parameter (see line 321). This results in the invocation of the constructor HTTPBuilder(Object) with the Map as the default URI. The URIBuilder attempts to convert the Map to a URI and eventually the process throws a URISyntaxException. Ideally, the no arg constructor should be called on HTTPBuilder.
This problem is occurring with http-builder 0.5.2.
Sample test.
import grails.test.mixin.* import groovyx.net.http.HTTPBuilder @TestFor(MyService) class MyServiceTests { void testMocks() { def httpBuilderController = mockFor(HTTPBuilder) def httpBuilder = httpBuilderController.createMock() } }
Here's the stacktrace.
java.net.URISyntaxException: Illegal character in path at index 0: {}
at java.net.URI$Parser.fail(URI.java:2809)
at java.net.URI$Parser.checkChars(URI.java:2982)
at java.net.URI$Parser.parseHierarchical(URI.java:3066)
at java.net.URI$Parser.parse(URI.java:3024)
at java.net.URI.<init>(URI.java:578)
at groovyx.net.http.URIBuilder.convertToURI(URIBuilder.java:92)
at groovyx.net.http.HTTPBuilder.setUri(HTTPBuilder.java:789)
at groovyx.net.http.HTTPBuilder.<init>(HTTPBuilder.java:219)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
at org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:71)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1460)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1390)
at groovy.lang.ExpandoMetaClass.invokeConstructor(ExpandoMetaClass.java:675)
at org.codehaus.groovy.runtime.InvokerHelper.invokeConstructorOf(InvokerHelper.java:824)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:329)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.asType(DefaultGroovyMethods.java:17582)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.asType(DefaultGroovyMethods.java:8282)
at org.codehaus.groovy.runtime.dgm$60.invoke(Unknown Source)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at grails.test.GrailsMock.createMock(GrailsMock.groovy:91)
at grails.test.GrailsMock$createMock.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
at MyServiceTests.testMocks(MyServiceTests.groovy:10)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
This happens because an empty map is passed in case of a concrete class and then a type transformation is called :
DefaultGroovyMethods.castToType(obj, type).
For Groovy, this means that if the type is not a primitive, a primitive wrapper, an enum, an array or a Number, it will try to build args if the object is a collection, an array of objects, or a Map. After that, it will invoke a constructor of the type with the args.
Here we are passing an empty Map as argument, which is why Grails mocking will fail to work properly for any concrete class with a constructor expecting a Map or an Object or a varargs.
After asType is done, it will return an instance of the concrete class (it would return a Proxy if it were an interface) and then for any method call, it will look in the expando of the demand object (the one you feed with demand.method {} declarations) and if nothing is there it will throw an expectation exception.