В ознакомительном примере 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 и провалит тест.
@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 для всех остальных типов.
MockType.Strict ещё больше закручивает гайки — для mock’а этого типа не только обязательно надо задавать поведение методов явно, но и именно в том порядке, в котором их будет проверять тестируемый код:
@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:
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():
@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 и поведенческие методы. А тестированию поведения будет посвящена отдельная статья.