В ознакомительном примере EasyMock, я писал: «в аннотации @Mock я указал, что хочу так называемый “nice mock”. На самом деле, аннотация @Mock(type = MockType.NICE) создает full nice non-strict mock, что означает «mock с подменой всех методов, поведением по умолчанию и без проверки порядка вызовов». Какие ещё есть варианты mock’ов?
Поскольку рассуждать мы будет в основном о поведении разных видов mock-объектов, весь код будет содержаться в самой статье, без отдельного примера.
Nice/Default/Strict
В аннотацию @Mock передаётся параметр type, который может принимать три значения:
- MockType.NICE
- MockType.DEFAULT
- MockType.STRICT
Причем значение по умолчанию… MockType.DEFAULT которое создаёт mock требующий явного задания поведения через вызов expect() и если вызвать метод, для которого поведение не задано, EasyMock выбросит AssertionError и провалит тест.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Mock(type = MockType.NICE)
private Service niceMock;
@Mock
private Service defaultMock;
@Test
public void testNice() { //This will pass
replay(niceMock);
assertThat(niceMock.numericMethod(), is(0));
assertFalse(niceMock.booleanMethod());
}
@Test
public void testDefault() {
expect(defaultMock.numericMethod()).andReturn(5);
replay(defaultMock);
assertThat(defaultMock.numericMethod(), is(5)); //Pass
assertTrue(defaultMock.booleanMethod());//Fail
}
|
В отличие от default типа, mock с MockType.NICE создаёт mock с поведением по умолчанию: для каждого не private и не final метода класса будет возвращено значение по умолчанию, если поведение метода не задано:
- 0 для числовых типов
- false для Boolean типа
- null для всех остальных типов.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Mock(type = MockType.Strict)
private Service mock
@Test
public void testCorrectOrder() {
expect(mock.getFirst()).andReturn(1);
expect(mock.getSecond()).andReturn(2);
replay(mock);
assertThat(mock.getFirst(), is(1)); //pass
assertThat(mock.getSecond(), is(2)); //pass
}
@Test
public void testIncorrectOrder() {
expect(mock.getFirst()).andReturn(1);
expect(mock.getSecond()).andReturn(2);
replay(mock);
assertThat(mock.getSecond(), is(2)); //fail
assertThat(mock.getFirst(), is(1)); //
}
|
Partial Mock
Аннотация @Mock создаёт full mock объект, у которого все не final и не private методы заменены на методы, сгенерированные EasyMock. Однако иногда требуется заменить только некоторые методы, например когда тестируемый класс вызывает сам себя и мы тестируем вызывающий код. В этом случае можно вручную создать частичный (partial) mock:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Service {
String getName() { return "World"; }
String getGreeting() { return "Hello " + this.getName(); }
}
// somewhere in test
@Test
public void testGreeting() {
Service testedObject = partialMockBuilder(Service.class)
.addMockedMethod("getName")
.createMock();
expect(testedObjet.getName()).andReturn("TEST");
replay(testedObject);
assertThat(testedObject.getGreeting(), is("Hello TEST"));
}
|
Надо отметить, что в partial mock стандартные методы класса Object, такие как equals(), hashCode(), toString(), finalize() не будут заменены, если их явно не указать в addMockedMethod(). Это отличается от поведения full mock, у которого эти методы так же заменяются на EasyMock реализации.
Stub mocks
Stub mock это не совсем разновидность mock’а, это вариант его использования. Выше, говоря про strict mock’и я говорил, что для этого типа mock-объектов проверяется порядок вызовов, то есть поведение. Для default mock’ов поведение тоже проверятся: количество вызовов, обязательность вызовов, параметры итд. Но, если вы тестируете результат выполнения, а не поведение, то все эти возможности только мешают.
Например у вас есть зависимость, у которой можно спросить текущего пользователя. Некоторые методы тестируемого класса вызывают этот метод, некоторые не вызывают, а некоторые вызывают дважды. Описывать это поведение для каждого теста не только неудобно, но и вызывает дополнительную работу при рефакторинге. При том, что поведение этого метода нам вообщем-то неинтересно вовсе.
Решение этой проблемы в замене результата вызова expect():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Mock
private UserService userService;
@TestedObject
private DataService dataService;
@Before
public void setUp() {
expect(userService.getCurrentUser).andStubReturn(EXAMPLE_USER);
replay(userService);
}
@Test
public void testSomething() {
assertThat(dataService.getDataForUser(), is(SAMPLE_DATA));
}
|
andStubReturn() говорит EasyMock, что мы не беспокоимся о том, когда вызван этот метод, сколько раз он вызван или вызыван ли вообще.
Stub методы можно использовать и с исключениями и с динамически генерируемыми ответами. В одном mock объекте можно смешивать и stub и поведенческие методы. А тестированию поведения будет посвящена отдельная статья.