EasyMock mocks: nice, default, strict, stub и partial

В ознакомительном примере 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 и поведенческие методы. А тестированию поведения будет посвящена отдельная статья.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *