How to keep service's metaClass from being ove

2019-09-04 05:12发布

问题:

I'm trying to mock a call to an outside service in an integration test, the service is used in a grails webflow. The service isn't in flow or conversation scope, but is added via dependency injection see here.

I've managed to figure out a way to override a service via replacing it's metaClass using ExpandoMetaClass. The changes only work when the test is ran alone, if another test that uses that same service is ran before this test, the metaClass changes are gone.

Part that overrides the metaClass:

static {
    ExpandoMetaClass someService = new ExpandoMetaClass(Object, false)
    someService.invokeMethod = { String name, args ->
        def result = 'success'
        if(name.equals('accessAnotherSystem')
        {
            StackTraceUtils.sanitize(new Throwable()).stackTrace.each
            {
                if(it.methodName.equals('test_method_I_Want_failure_in')
                {
                    result = 'exception'
                }
            }
            return result
        }

        def validMethod = SomeService.metaClass.getMetaMethod(name, args)
        if (validMethod != null)
        {
            validMethod.invoke(delegate, args)
        }
        else
        {
            SomeService.metaClass.invokeMissingMethod(delegate, name, args)
        }
    }
    someService.initialize()
    SomeService.metaClass = someService
}

Related question: How to change a class's metaClass per test

Is there a way to keep my changes for the test, or is there some other way to override the service.

回答1:

There is a simpler way if you want to override the service in your test case per test method. Look at an example:

class SomeControllerSpec extends Specification {

    def someService

    void "test any external method for success response"() {
        given: "Mocked service method"
        someService.metaClass.accessAnotherSystem = { arg1, arg2 ->
            return "some success response"
        }

        when: "Any controller method is called which calls this service method"
        // Your action which calls that service method

        then: "Result will processed coming from mocked method"
        // Your code
    }
}

You can do this same for any service test method. If you want to mock the method of same service for which you are writing test case then here yo go..

class SomeServiceSpec extends Specification {

    def someService

    void "test external method call"() {
        given: "Mocked service method"
        someService.metaClass.methodA = { arg1, arg2 ->
            return "some success response"
        }

        when: "A method is called which invokes the another method"
        // Your another service method which call the same method
        someService.methodB()    // Where methodB() invokes the methodA() internally in your code

        then: "Result will processed coming from mocked method"
        // Your code
    }
}