Mockテスト
Mockito + Powermock ≒ JMockit
JMockit
http://code.google.com/p/jmockit/
基本的な使い方
public class Target {
public String method() {
XxxService service = new XxxService();
return service.doExecute();
}
}
import mockit.Mocked;
import mockit.Expectations;
import mockit.NonStrictExpectations;
@Mocked
private XxxService xxxService;
@Test
public void test(){
new Expectations(xxxService) {
{
xxxService.doExecute();
returns("xxx");
}
};
new NonStrictExpectations() {
{
Account account = new Account();
account.setBalance(1000);
xxxService.getAccount("xxx");
returns(account);
}
};
String result = new Target().method();
...
}
privateメソッドのMock化
@Mocked("method")
private XxxService xxxService;
@Test
public void test(){
new NonStrictExpectations() {
{
invoke(xxxService,
"method",
withAny("xxx"),
withAny(new Integer(10)));
result = "success";
}
};
xxxService.doExecute();
...
}
PowerMock
http://code.google.com/p/powermock/
※導入前に設計を見直すべき
<!-- JUnit -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<!-- TestNG -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.powermock.api.mockito.PowerMockito;
// JUnit
//@RunWith(PowerMockRunner.class)
//@PrepareForTest({Target.class})
//public class MockTest {
// TestNG
@PrepareForTest({Target.class})
public class MockTest extends PowerMockTestCase {
@Mock
private Local local;
// NOTE: Custom arg × (DataProviderとPowerMockTestCase間の問題)
@Test(dataProvider = "...")
public void shouldXxx(Object arg) {
PowerMockito.whenNew(Local.class)
.withNoArguments().thenReturn(local);
doReturn((Custom)arg).when(local).doSomething();
...
}
}
class Target {
public void method() {
...
Local local = new Local();
...
Custom c = local.doSomething();
...
}
}
※注意点
・JaCoCo(Sonarに組み込む)、EclEmmaを利用する場合、
@PrepareForTestをつけるクラスのCoverageは0%になる
・Coberturaを利用する場合、OK
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.6</version>
</plugin>
Mockito(続く)
Mockito cannot mock:
・final classes
・enums
・final methods
・static methods
・private methods
・hashCode() and equals()
Mock
thenReturn(T valueToBeReturned)
thenThrow(Throwable toBeThrown)
thenThrow(Class<? extends Throwable> toBeThrown)
then(Answer answer)
thenAnswer(Answer answer)
thenCallRealMethod()
doThrow(Throwable toBeThrown)
doThrow(Class<? extends Throwable> toBeThrown)
doAnswer(Answer answer)
doCallRealMethod()
doNothing()
doReturn(Object toBeReturned)
Argument Matching
any()
any(Class<T> clazz)
anyBoolean(), anyByte(), anyChar()
anyDouble(), anyFloat(), anyInt()
anyLong(), anyShort(), anyString()
anyCollection(), anyList(),anyMap()
anySet()
anyCollectionOf(Class<T> clazz), anyListOf(Class<T> clazz)
anyMapOf(Class<T> clazz),
anySetOf(Class<T> clazz)
anyVararg()
eq(T value)
isNull, isNull(Class<T> clazz)
isNotNull, isNotNull(Class<T> clazz)
isA(Class<T> clazz)
refEq(T value, String... excludeFields)
matches(String regex)
startsWith(string),
endsWith(string),
contains(string)
aryEq(PrimitiveType value[])
aryEq(T[] value)
cmpEq(Comparable<T> value)
gt(value), geq(value), lt(value),
leq(value)
argThat(org.hamcrest.Matcher<T>
matcher)
booleanThat(Matcher<Boolean> matcher),
byteThat(matcher),
charThat(matcher),
doubleThat(matcher),
floatThat(matcher),
intThat(matcher),
longThat(matcher),
shortThat(matcher)
and(first, second), or(first, second),
not(first)
Verify
times(int wantedNumberOfInvocations)
never()
atLeastOnce()
atLeast(int minNumberOfInvocations)
atMost(int maxNumberOfInvocations)
only()
timeout(int millis)
★initMocksについて
@BeforeMethod
public void setup() {
MockitoAnnotations.initMocks(this);
}
同様
@BeforeClass
public void setup() {
MockitoAnnotations.initMocks(this);
}
@AfterMethod
public void setup() {
Mockito.reset(mock1, mock2);
}
★@Mockのanswerオプション
RETURNS_SMART_NULLS
@Mock(answer=Answers.RETURNS_SMART_NULLS)
private Foo mock;
Stuff stuff = mock.getStuff();
stuff.doSomething();
RETURNS_DEEP_STUBS
@Mock(answer=Answers.RETURNS_DEEP_STUBS)
private Foo mock;
when(mock.getBar().getName()).thenReturn("deep");
assertEquals("deep", mock.getBar().getName());
CALLS_REAL_METHODS
@Mock(answer=Answers.CALLS_REAL_METHODS)
private Foo mock;
value = mock.getSomething();
when(mock.getSomething()).thenReturn(fakeValue);
value = mock.getSomething();
★InOrder用法
@Mock
private List singleMock;
singleMock.add("first");
singleMock.add("second");
InOrder inOrder = Mockito.inOrder(singleMock);
inOrder.verify(singleMock).add("first");
inOrder.verify(singleMock).add("second");
@Mock
private List firstMock;
@Mock
private List secondMock;
firstMock.add("first");
secondMock.add("second");
InOrder inOrder = Mockito.inOrder(firstMock, secondMock);
inOrder.verify(firstMock).add("first");
inOrder.verify(secondMock).add("second");
Mockito
https://code.google.com/p/mockito/
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
基本的な使い方
public class MyService {
public String method() {
return "xxx";
}
public String anotherMethod() {
return method() + "," + method();
}
}
import static org.mockito.Mockito.*;
public class MyServiceTest {
@Test
public void testMock() {
// クラスごと(対象クラスの実装に依存せず)
MyService myService = mock(MyService.class);
when(myService.method()).thenReturn("xxx");
String actual = myService.method();
assertEquals(actual, "xxx");
// 特定のメソッドのみ(対象クラスの実装に依存)
MyService myService = spy(new MyService());
when(myService.method()).thenReturn("xxx");
String actual = myService.anotherMethod();
assertEquals(actual, "yyy");
}
}
サンプル
public class SampleA {
private SampleB sampleB;
private SampleC sampleC;
public String createName() {
return sampleB.getName("xxx");
}
public String createPrice() {
int id = sampleC.getId();
int price = sampleC.getPrice(id);
return "id:" + id + " price:" + price;
}
}
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
public class SampleTest {
@Mock
private SampleB sampleBMock;
@Spy
private SampleC sampleCMock = new SampleC();
@InjectMocks //テスト対象
private SampleA sampleA = new SampleAImpl();
@InjectMocks //複数の@InjectMocksが使える;フィールドもMockできる
@Spy
private SampleD sampleD = new SampleDImpl();
※@InjectMocksと@Spyは一緒に使えるが、@InjectMocksと@Mockは×
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testVerifyMock() {
when(sampleBMock.get(0)).thenReturn("first");
when(sampleBMock.get(1)).thenThrow(new RuntimeException());
when(sampleBMock.getName(anyString())).thenReturn("mock");
//連続
when(sampleBMock.someMethod("xxx"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
when(sampleBMock.someMethod("xxx"))
.thenReturn("one", "two", "three");
String ret = sampleA.createName();
assertEquals("mock", ret);
verify(sampleBMock, times(1)).getName("xxx");
verify(sampleBMock).getName("xxx"); // times(1)はデフォルト
verify(sampleBMock, never()).getName("never");
verify(sampleBMock, atLeastOnce()).add("three times");
verify(sampleBMock, atLeast(2)).add("five times");
verify(sampleBMock, atMost(5)).add("three times");
verify(sampleBMock).get(anyInt());
verify(sampleBMock).someMethod(
anyInt(), anyString(), eq("third argument"));
verify(sampleBMock, timeout(100)).someMethod();
verify(sampleBMock, timeout(100).times(2)).someMethod();
// voidメソッドのスタブ化
doThrow(new RuntimeException()).when(sampleBMock).clear();
doNothing()
.doThrow(new RuntimeException())
.when(sampleBMock).someVoidMethod();
//上書きの場合
when(sampleBMock.foo()).thenThrow(new RuntimeException());
// when(sampleBMock.foo()).thenReturn("bar"); //×
doReturn("bar").when(sampleBMock).foo();
}
@Test
public void testVerifySpy() {
doReturn(100).when(sampleCMock).getPrice(anyInt());
String ret = sampleA.createPrice();
assertEquals("id:2 price:100", ret);
when(sampleCMock.size()).thenReturn(100);
List list = new LinkedList();
List spy = spy(list);
//when(spy.get(0)).thenReturn("foo"); //例外
doReturn("foo").when(spy).get(0);
assertThat(list.get(0), is("foo"));
}
}
※後置記法と前置記法(NOTE:戻り値がvoidのメソッドは前置記法で)
when(mock.get(0)).thenReturn("xxx");
when(mock.get(1)).thenThrow(new IndexOutOfBoundsException());
doReturn("xxx").when(mock).get(0);
doThrow(new IndexOutOfBoundsException()).when(mock).get(1);
★ArgumentCaptor用法
基本
@Mock
private List mock;
@Captor
private ArgumentCaptor<MyClass> captor;
mock.add(new MyClass("foo"));
//verify(mock).add(new MyClass("foo"));
verify(mock).add(captor.capture());
MyClass argument = captor.getValue();
assertEquals("foo", argument.getName());
サンプル
public class PersonService{
@Inject
private PersonDao personDao;
public boolean update(Integer personId, String name){
Person person = personDao.findPerson(personId);
if(person != null){
Person updatedPerson =
new Person(person.getPersonID(), name);
personDao.update(updatedPerson);
return true;
}
return false;
}
}
import static org.junit.Assert.*;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
public class PersonServiceTest{
@Mock
private PersonDao personDAO;
@InjectMocks
private PersonService personService;
@Captor
private ArgumentCaptor<Person> personCaptor;
@Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldUpdatePersonName(){
Person person = new Person(101, "xxx");
when(personDAO.findPerson(101)).thenReturn(person);
boolean updated = personService.update(101, "yyy");
assertTrue(updated);
verify(personDAO).findPerson(101);
verify(personDAO).update(personCaptor.capture());
Person updatedPerson = personCaptor.getValue();
assertEquals("yyy", updatedPerson.getPersonName());
verifyNoMoreInteractions(personDAO);
}
@Test
public void shouldNotUpdateIfPersonNotFound(){
when(personDAO.findPerson(101)).thenReturn(null);
boolean updated = personService.update(101, "yyy");
assertFalse(updated);
verify(personDAO).findPerson(101);
verifyZeroInteractions(personDAO);
verifyNoMoreInteractions(personDAO);
}
}
★Answer用法
サンプル1
@Spy
private Logger logger;
final StringBuilder sb = new StringBuilder();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
sb.append(invocation.getArguments()[0]);
invocation.callRealMethod();
return null;
}
}).when(logger).info(anyString());
assertThat(sb.toString(), is("xxx"));
サンプル2
@Mock
private Sample mock;
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
theString = (String) invocation.getArguments()[0];
return null;
}
}).when(mock).setString(anyString());
when(mock.getString()).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return theString;
}
});
mock.setString("foo");
assertEquals("foo", mock.getString());
サンプル3
@Inject
private PersonJobDao personJobDao;
public PersonJob createPersonJob(Person person, Job job) {
...
PersonJob personJob = new PersonJob(person, job);
return personJobDao.create(personJob);
}
@Mock
private PersonJobDao personJobDao;
@Test
public void shouldXxx() {
when(personJobDao.create(any(PersonJob.class)))
.thenAnswer(new Answer<PersonJob>() {
public PersonJob answer(InvocationOnMock invocation)
throws Throwable {
return (PersonJob) invocation.getArguments()[0];
}
});
Person person = new Person();
Job job = new Job();
PersonJob personJob = personJobManager.createPersonJob(person, job);
assertThat(personJob.getPerson(), is(person));
assertThat(personJob.getJob(), is(job));
}