When I wrote a test with Spring Boot, I didn't know how to test the private method and how to mock the private method, so I will summarize it. It seems that you cannot mock private methods with Mockito alone, so use PowerMock together.
Prepare a class to be tested appropriately. Let's call it the HelloService
class.
import org.springframework.beans.factory.annotation.Autowired;
public class HelloService {
@Autowired
private HelloRepository repository;
public String getFromRepo() {
return repository.getData();
}
public String methodPublic(String arg) {
return "method public: " + methodPrivate(arg);
}
private String methodPrivate(String arg) {
return "method private, arg: " + arg;
}
}
Create a class that the HelloService
class depends on.
public class HelloRepository {
public String getData() {
return "HelloRepository.getData";
}
}
--The methodPrivate
method is a private method. [Testing private methods](#private method testing) describes how to test this method.
--The methodPublic
method is a public method, and the private method is called inside. [Chapter of how to partially mock a private method](#Private method is partially mocked) explains how to partially mock a private method in the same class.
--getFromRepo
calls the method of the dependent HelloRepository. In [Chapter for using with Mockito's mock](using with #mockito's mock), test it in a way that coexists with the previous code.
Here, the test method of the methodPrivate
method, which is a private method of the above-mentioned test target class HelloService
class, will be explained.
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class HelloServiceTest {
private HelloService service;
@Before
public void setUp() {
service = new HelloService();
}
@Test
Public void method Private test() throws Exception {
Method method = HelloService.class.getDeclaredMethod("methodPrivate", String.class);
method.setAccessible(true);
String actual = (String)method.invoke(service, "hoge");
assertThat(actual, is("method private, arg: hoge"));
}
}
--You can get the method instance of the test target class by using test target class.class.getDeclaredMethod (test target method name, argument type)
.
--If you set method.setAccessible (true), you can also call it with a private method.
--The method can be called with method.invoke (argument). Since the return value will be Object, cast it.
--If you get a warning saying ʻUnchecked cast, add
@SuppressWarnings ("unchecked") `in the line above and it will disappear
Here, we will explain how to partially mock the private method methodPrivate
called in the above when testing the methodPublic
method of the test target class HelloService
class.
Since PowerMock is used, add the following to build.gradle (If you are using maven, please read as appropriate)
dependencies {
testCompile('org.powermock:powermock-module-junit4:1.6.2')
testCompile('org.powermock:powermock-api-mockito:1.7.3')
}
Write a test
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.lang.reflect.Method;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;
@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloService.class)
public class HelloServiceTest {
private HelloService service;
@Before
public void setUp() {
service = spy(new HelloService());
}
@Test
Public void method Public test() throws Exception {
when(service, "methodPrivate", "fuga").thenReturn("hoge");
String actual = service.methodPublic("fuga");
verifyPrivate(service, times(1)).invoke("methodPrivate", "fuga");
assertThat(actual, is("method public: " + "hoge"));
}
}
--Added two annotations to the class. @RunWith (PowerMockRunner.class)
and @PrepareForTest (tested.class)
--In order to create a partial mock, spy (instance of the class under test)
is called insetUp ()
. The point here is to use PowerMock's spy (ʻorg.powermock.api.mockito.PowerMockito.spy) instead of Mockito's spy. --The same is true for
when (), using PowerMock when. I'm using ʻorg.powermock.api.mockito.PowerMockito.when
.
――You can make a partial mock without worrying about whether it is private or public.
--Use verifyPrivate
to verify that it was called.
――The usability of when and verify seems to be the same as Mockito
Add a test for the getFromRepo
method.
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;
@RunWith(PowerMockRunner.class)
@PrepareForTest(HelloService.class)
public class HelloServiceTest {
@Mock
private HelloRepository repository;
@InjectMocks
private HelloService service;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
service = spy(service);
}
@Test
Public void getFromRepo test() {
when(repository.getData()).thenReturn("hoge");
assertThat(service.getFromRepo(), is("hoge"));
verify(service, times(1)).getFromRepo();
}
@Test
Public void method Public test() throws Exception { //Same as above}
@Test
Public void method Private test() throws Exception { //Same as above}
}
--Mocking the dependent Hello Repository
.
--Annotate the field you want to mock with @ Mock
and annotate the field you want to mock with @InjectMocks
.
--If you call MockitoAnnotations.initMocks (this);
in setUp ()
, it seems that it will inject a mock.
――I was worried if it would coexist with the above PowerMock, but it worked fine if I simply did service = spy (service);
after initMocks.
PowerMock was convenient.
I used Mockito when mocking non-private methods (like HelloRepository in this case), and I thought it would be useful for @ Mock
or @InjectMocks
.
When I tried to use PowerMock to mock a private method, I thought it would be confusing if the method changed drastically, but I'm relieved that I can write it in the same way.
Recommended Posts