Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 2.0 final
    • Fix Version/s: None
    • Component/s: Testing
    • Labels:

      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)
      
      

        Activity

        Hide
        Guillaume Balaine added a comment -

        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.

        Show
        Guillaume Balaine added a comment - 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.
        Hide
        Guillaume Balaine added a comment - - edited

        There are multiple ways to get it to work, but basically you cannot cast a Proxy to a concrete class (you won't be able to set the HttpBuilder you're using in your service).
        You can manipulate the metaClass but this is heavy and probably not the best way to simulate mocking especially since you won't get automatic verification of method call expectations (the things that fire AssertionErrors).
        I think you have to use Groovy Interceptors (at least for now), if you really want to mock HttpBuilder :

        		MockFor mockBuilder = new MockFor(HTTPBuilder)
        		def requestDelegate = [response: [:], uri: [:]]
        		def result 
        		mockBuilder.demand.request {Method method, contentType, body ->
        			body.delegate = requestDelegate
                                body.setResolveStrategy(Closure.DELEGATE_FIRST)
        			body.call()
        			requestDelegate.response[result].call(requestDeletage.response)
        		}
        		mockBuilder.use {
        			result = '404' //Example response handler that you set in your request closure parameter
        			service.yourMethod()
        		}
        

        I think the problem is that you have to manage the closure overhead yourself.
        After thinking a little, I think it would be much smarter to just mock the client and let the builder handle results as it usually would.

        Show
        Guillaume Balaine added a comment - - edited There are multiple ways to get it to work, but basically you cannot cast a Proxy to a concrete class (you won't be able to set the HttpBuilder you're using in your service). You can manipulate the metaClass but this is heavy and probably not the best way to simulate mocking especially since you won't get automatic verification of method call expectations (the things that fire AssertionErrors). I think you have to use Groovy Interceptors (at least for now), if you really want to mock HttpBuilder : MockFor mockBuilder = new MockFor(HTTPBuilder) def requestDelegate = [response: [:], uri: [:]] def result mockBuilder.demand.request {Method method, contentType, body -> body.delegate = requestDelegate body.setResolveStrategy(Closure.DELEGATE_FIRST) body.call() requestDelegate.response[result].call(requestDeletage.response) } mockBuilder.use { result = '404' //Example response handler that you set in your request closure parameter service.yourMethod() } I think the problem is that you have to manage the closure overhead yourself. After thinking a little, I think it would be much smarter to just mock the client and let the builder handle results as it usually would.

          People

          • Assignee:
            Unassigned
            Reporter:
            David Reines
          • Votes:
            2 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Last Reviewed:

              Development