1

In unit test cases of controllers i'm only able to inject the services that are used in the corresponding controller.But if say one service in controller injects another service then my test cases gets failed i.e. can not invoke service method on null object.

@TestFor(MyController)
@Mock(MyService)
class MyControllerSpec extends Specification {
    void "test something"(){
        when:
            controller.method1();
        then:
            //something
    }

}

class MyController(){
    MyService myService

    void method1(){
        myService.serviceMethod()       
    }   
}

class MyService(){
    AnotherService anotherService
    void serviceMethod(){
        anotherService.anotherServiceMethod()
    }
}

class AnotherService(){
    void anotherServiceMethod(){
    \\something
    }
}

in this case, I'm getting can not invoke "anotherServiceMethod" on null object. is there any way to test this type of controller? and is it a good approach to inject a service in another service?

YoYo Honey Singh
  • 464
  • 1
  • 4
  • 21

1 Answers1

9

It is a good approach to inject serice into another service, nothing wrong with that.

To make this test working there are few approaches.

Recommended - unit test should test behaviour of only single class, if you need to test full functionality, integration/functional spec is better to do that. In such case, you execute methods of your controller, but all other classes which are called are mocks on which you predict what values are returned. Then you create separate tests for MyService and AnotherService. In such case, your test could look like:

@TestFor(MyController)
class MyControllerSpec extends Specification {
    void "test something"(){
        given:
            MyService myService = Mock()
            controller.myService = myService
        when:
            controller.method1();
        then:
            1 * myService.serviceMethod() >> someResult
            //something
    }
}

this test ensures, that serviceMethod() is called and you force it to return something what you expect for this spec. If in other case (exception thrown, if/else you want to be sure that serviceMethod() is not called, you can use 0 * myService.serviceMethod()

Not recommended: If you insist that service method should be called in this spec, you can create mock of AnotherService and set it on the service which is available in controller. Something like:

AnotherService anotherService = Mock()
controller.myService.anotherService = anotherService
...
then:
1 * anotherService.callAnotherMethod()

Maybe also using @Mock([MyService, AnotherService]) will work, but I didn't test it. We test integrations with integration tests - there everything is injected for you and you work on normal classes.

practical programmer
  • 1,604
  • 10
  • 15
  • it means i do not need to actually go in deep of calling methods. i can provide what i'm expecting from a function call and then test the method? – YoYo Honey Singh Mar 04 '16 at 18:46
  • is there a documentation available for the same,because i dont know about the 0* ... 1* notation.? – YoYo Honey Singh Mar 04 '16 at 18:47
  • Yes, Grails uses Spock Framework. Just read docs from: http://spockframework.github.io/spock/docs/1.0/index.html and Unit Testing part of Grails app for test specific to Grails artifacts – practical programmer Mar 04 '16 at 19:03
  • And yes, you don't need to go in deep of calling methods - this is the main idea of unit testing just single class and mocking others, you just say what methods you expected to be called on which classes and how your class behave for different results of those methods. – practical programmer Mar 04 '16 at 19:05
  • now I'm able to mock functions of a service, but not of a controller. Say my controller method under testing calls another controller method, when I mock that method using "controller.secondMethod" it doesn't work. – YoYo Honey Singh Mar 05 '16 at 09:15
  • This is correct. If methods of class under test would be mocked you would test nothing :) . If you really need to override such method, for example because of some database access there are two ways: better but with effort, refactor your code and move this to another class which can be mocked, this makes your code better, but if for some reason this is not what you want or don't have time, you can use meta programming. For example use `controller.metaClass.secondMethod = { params -> new method body}`. It is essential that params part of closure has the same parameters as overriden method – practical programmer Mar 05 '16 at 09:58
  • @TestFor(MyController) @Mock(MyService) class MyControllerSpec extends Specification { void "test something"(){ when: controller.method1(); then: 1 * controller.method2(); } } class MyController(){ void method1(){ this.method2(); } void method2(){ //something } } – YoYo Honey Singh Mar 05 '16 at 16:44
  • when i'm trying to check whether the method2 function gets called in "then:" block, it's showing 'Too few invocations' error – YoYo Honey Singh Mar 05 '16 at 16:46
  • Controller is neither `Mock` nor `GroovyMock` so you cannot use `1 * controller` or any syntax like that for it – practical programmer Mar 05 '16 at 19:37