Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock only one method of one injected dependency and inject the rest normally?

Tags:

I'm writing tests for a project with a heavy use of dependency injection.

So normally I would just inject the object that I'm testing:

public class RegistrationTest
        extends WithApplication {

    private RegistrationController controller;

    @Before
    public void setUp() throws Exception {
        Injector injector = app.injector();
        controller = injector.instanceOf(RegistrationController.class);
    }

    @Test
    public void openRegistrationView() {
        Result result = controller.registrationForm();
        assertEquals(OK, result.status());
    }
}

Now I need to mock a return value of a method of one of the dependencies of the class under test.

To do this I can use @InjectMocks and @RunWith(MockitoJUnitRunner.class) to inject mocked dependencies.

@RunWith(MockitoJUnitRunner.class)
public class RegistrationTest
        extends WithApplication {

    @InjectMocks
    private RegistrationController controller;
    @Mock
    private FormFactory formFactory;
    @Mock
    private RegistrationIpCache registrationIpCache;

    @Test
    public void openRegistrationView() {
        doReturn("test").when(registrationIpCache).getLast();
        Result result = controller.registrationForm();
        assertEquals(OK, result.status());
    }
}

But while I only want to mock one method call of RegistrationIpCache I need to mock the whole class and also I need to mock all other dependencies of RegistrationController like FormFactory in my example. Now every method of all mocked classes doesn't work as expected.

Sure I can use doCallRealMethod() to mock every method of all dependencies

doCallRealMethod().when(formFactory).form(Registration.class);

But as you already can imagine the work and the amount of boilerplate code needed for this to work is extreme.

Isn't it possible to inject my class normally and then only spy one dependency or mock one method of one dependency?

like image 213
Spenhouet Avatar asked Sep 06 '17 13:09

Spenhouet


1 Answers

You need to use @Spy instead of a @Mock.

@InjectMocks
private RegistrationController controller;
@Mock
private FormFactory formFactory;
@Spy
private RegistrationIpCache registrationIpCache;

But be aware that in this case @Spy will try to use default constructor. If the default constructor is not available use explicit constructor call:

@Spy
private RegistrationIpCache registrationIpCache = new RegistrationIpCache(dependencies);

Here is the cases when Mockito isn't able to instantiate your class from @Spy javadoc:

Mockito will try to find zero argument constructor (even private) and create an instance for you. But Mockito cannot instantiate inner classes, local classes, abstract classes and interfaces.

Here is the example of manually injecting dependencies:

@RunWith(MockitoJUnitRunner.class)
public class RegistrationTest {
    private RegistrationController controller;

    private RegistrationIpCache spyRegistrationIpCache; //this is the dependencies that you need to spy

    @Before
    public void setUp() throws Exception {
        spyRegistrationIpCache = spy(realInstanceOfregistrationIpCache);

        controller = new RegistrationController(registrationIpCache, realInstanceOfFormFactory);
    }
}

In this case there is much more code that you have to write, but this give you much more control over the object instantiation and dependency injection. Note that using constructor instantiation is not required. Since you do it manually you can instantiate it however you want.

like image 160
Sergii Bishyr Avatar answered Sep 30 '22 13:09

Sergii Bishyr