Статьи

Используйте Mockito для макета полей с автопроводкой

Внедрение зависимостей — очень мощная особенность контейнеров Inversion of Control, таких как Spring и EJB. Всегда полезно инкапсулировать введенные значения в частные поля. Но инкапсуляция автопроводных полей снижает тестируемость.

Мне нравится, как Mockito решил эту проблему, чтобы смоделировать поля с автопроводкой. Поясню это на примере. (В этом сообщении ожидается, что вы немного знакомы с синтаксисом Mockito, но он достаточно информативен.)

Вот первая зависимость модуля тестирования. Это бобы Синглтон-Спринг. Этот класс будет издеваться в тесте.

1
2
3
4
5
6
@Repository
public class OrderDao {
    public Order getOrder(int irderId){
        throw new UnsupportedOperationException("Fail is not mocked!");
    }
}

Вот вторая зависимость класса тестирования. Это также компонент Spring. Этот класс будет шпионить (частично издеваться) в тесте. Его метод calculatePriceForOrder будет вызываться без изменений. Второй метод будет заглушен.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Service
public class PriceService {
    public int getActualPrice(Item item){
        throw new UnsupportedOperationException("Fail is not mocked!");
    }
 
    public int calculatePriceForOrder(Order order){
        int orderPrice = 0;
        for (Item item : order.getItems()){
            orderPrice += getActualPrice(item);
        }
        return orderPrice;
    }
}

И вот класс под тестом. Это автоматически связывает зависимости выше.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Service
public class OrderService {
 
    @Autowired
    private PriceService priceService;
 
    @Autowired
    private OrderDao orderDao;
 
    public int getOrderPrice(int orderId){
        Order order = orderDao.getOrder(orderId);
        return priceService.calculatePriceForOrder(order);
    }
}

Наконец, вот тестовый пример. Используются аннотации на уровне поля:

  • @InjectMocks@InjectMocks экземпляр объекта тестирования и пытается ввести поля, @Mock или @Spy в частные поля объекта тестирования
  • @Mock — создает фиктивный экземпляр поля, которое он аннотирует
  • @Spy — создает шпион, например, аннотированное поле
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class OrderServiceTest {
    private static final int TEST_ORDER_ID = 15;
    private static final int TEST_SHOES_PRICE = 2;  
    private static final int TEST_SHIRT_PRICE = 1;
 
    @InjectMocks
    private OrderService testingObject;
 
    @Spy
    private PriceService priceService;
 
    @Mock
    private OrderDao orderDao;
 
    @BeforeMethod
    public void initMocks(){
        MockitoAnnotations.initMocks(this);
    }
 
    @Test
    public void testGetOrderService(){
        Order order = new Order(Arrays.asList(Item.SHOES, Item.SHIRT));
        Mockito.when(orderDao.getOrder(TEST_ORDER_ID)).thenReturn(order);
 
        //notice different Mockito syntax for spy
        Mockito.doReturn(TEST_SHIRT_PRICE).when(priceService).getActualPrice(Item.SHIRT);
        Mockito.doReturn(TEST_SHOES_PRICE).when(priceService).getActualPrice(Item.SHOES);
 
        //call testing method
        int actualOrderPrice = testingObject.getOrderPrice(TEST_ORDER_ID);
 
        Assert.assertEquals(TEST_SHIRT_PRICE + TEST_SHOES_PRICE, actualOrderPrice);
    }
}

Итак, что произойдет, когда вы запустите этот тест:

  1. Прежде всего, среда TestNG выбирает аннотацию @BeforeMethod и вызывает метод initMocks
  2. Этот метод вызывает специальный вызов Mockito ( MockitoAnnotations.initMocks(this) ) для инициализации аннотированных полей. Без этого вызова эти объекты были бы null . Распространенная ошибка при таком подходе — забыть этот вызов.
  3. Когда все поля теста заполнены желаемыми значениями, вызывается test.

Этот пример не включает создание контекста Spring, и аннотации Spring приведены здесь только в качестве примеров для использования против производственного кода. Сам тест не включает никакой зависимости от Spring и игнорирует все его аннотации. Фактически, вместо этого можно использовать аннотации EJB, или они могут работать с простыми (не управляемыми IoC) частными полями.

Разработчики склонны думать о MockitoAnnotations.initMocks(this) как о ненужных накладных MockitoAnnotations.initMocks(this) . Но на самом деле это очень удобно, потому что он сбрасывает объект тестирования и повторно инициализирует макеты. Вы можете использовать это например

  • Если у вас есть различные методы тестирования, использующие одни и те же аннотированные экземпляры, чтобы гарантировать, что различные тестовые прогоны не используют одинаковое записанное поведение
  • Когда используются повторяющиеся / параметризованные тесты. Например, вы можете включить этот вызов в сам метод теста и получить шпионский объект в качестве параметра теста (как часть теста). Эта способность очень @DataProvider в сочетании с @DataProvider TestNG @DataProvider (об этом мы расскажем в другом сообщении в блоге).

@Spy аннотированный объект может быть создан двумя способами

  • Автоматически с помощью инфраструктуры Mockito, если есть конструктор по умолчанию (не параметризованный)
  • Или явно инициализирован (например, когда есть только конструктор не по умолчанию)

Объект тестирования, аннотированный @InjectMocks также можно явно инициализировать.

Ссылка: Используйте Mockito для макета полей с автопроводкой от нашего партнера JCG Любоса Крнака на блоге http://lkrnac.net/ .