简体   繁体   中英

How to mock private methods in controller in grails 2.2.4

I have a private method which was mocked in grails 1.3.7 using metaclass but now that I upgraded grails version to 2.2.4, the same mocking fails.

Method to test has a call to private method

   private def MyPrivateMeth1(def arg1, def arg2) {
...
}

Mocking is something like this

MyController.metaClass.private.MyPrivateMeth1 = { a, b ->
... 
}

Try using the @TestFor annotation, which will give you a controller variable. You can then alter the metaclass of that, as well as incorporating Kamil Mikolajczyk and araxn1d's suggestions. So, your whole test should probably look like this:

@TestFor(MyController) 
class MyControllerTests { 

    setup() {
        controller.metaClass.MyPrivateMeth1 = {def arg1, def arg2 -> 
            //you can make your mocked method return whatever you wish here
            return [key1: "foo", key2: "bar"] 
        }
    }
    void testForSomething() {
        //Some code here that invokes an action or two in your controller which in turn invokes the private method
    }
}

Make sure to have def (or String, Long, or whatever declaration you use) on the arguments of your mock method precisely as they are on the actual private method in the controller, or your test will try to use the class's normal private method first.

Here's a similar test in Spock:

import spock.lang.Specification
import spock.lang.Unroll
import grails.test.mixin.*
import org.junit.*
@TestFor(MyController) 
class MyControllerSpec {

    def "test that thing"() {
        setup:
            controller.metaClass.MyPrivateMeth1 = {def arg1, def arg2 -> return someOutput }
        expect:
            controller.someAction() == expectedRender
        where: 
            someOutput                              | expectedRender
            [key1: "foo", key2: "bar"]              | "expected render from controller"
    }
}

看来你需要声明闭包参数的类型(如果参数有实际类型,则为100%,例如Long ,但不确定def,但你需要尝试):

MyController.metaClass.MyPrivateMeth1 = { def a, def b -> ... }

I believe you don't need the .private. part

MyController.metaClass.MyPrivateMeth1 = { a, b -> ... }

should be enough, however I would specify parameter types explicitly

And, by the way, you should keep java naming conventions - methods names should start with lowercase character

For unit tests I have used Reflection for private methods. Something similar to this should work...

Method method = BehaviourService.getDeclaredMethod("behaviourValidConstraints",User.class,Behaviour.class)
method.setAccessible(true)
boolean valid = ((Boolean)method.invoke(service, user,b)).booleanValue()

First you get the method with getDeclaredMethod setting the name and the parameter types, you set it accesible and finally tou call it with method.invoke passing the object that has the method and the parameters. The result is an Object so you have to cast it.

I know there must be a better solution, but this one is the only one I've found that works

Edit : Sorry, what's above is for making a call to a private method. I think that I've mocked a private method before just doing...

MyController.metaClass.myPrivateMeth1 { a, b ->
... 
}

Just like you wrote it but without the .private and the = sign. Also, as Kamil said, you should follow java naming conventions for method names...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM