Внедрение зависимостей — очень мощная особенность контейнеров Inversion of Control, таких как Spring и EJB. Всегда полезно инкапсулировать введенные значения в частные поля. Но инкапсуляция автопроводных полей снижает тестируемость.
Мне нравится, как Mockito решил эту проблему, чтобы смоделировать поля с автопроводкой. Поясню это на примере. (В этом сообщении ожидается, что вы немного знакомы с синтаксисом Mockito, но он достаточно информативен.)
Вот первая зависимость модуля тестирования. Это бобы Синглтон-Спринг. Этот класс будет издеваться в тесте.
|
1
2
3
4
5
6
|
@Repositorypublic 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
|
@Servicepublic 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
|
@Servicepublic 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); }} |
Итак, что произойдет, когда вы запустите этот тест:
- Прежде всего, среда TestNG выбирает аннотацию
@BeforeMethodи вызывает методinitMocks - Этот метод вызывает специальный вызов Mockito (
MockitoAnnotations.initMocks(this)) для инициализации аннотированных полей. Без этого вызова эти объекты были быnull. Распространенная ошибка при таком подходе — забыть этот вызов. - Когда все поля теста заполнены желаемыми значениями, вызывается test.
Этот пример не включает создание контекста Spring, и аннотации Spring приведены здесь только в качестве примеров для использования против производственного кода. Сам тест не включает никакой зависимости от Spring и игнорирует все его аннотации. Фактически, вместо этого можно использовать аннотации EJB, или они могут работать с простыми (не управляемыми IoC) частными полями.
Разработчики склонны думать о MockitoAnnotations.initMocks(this) как о ненужных накладных MockitoAnnotations.initMocks(this) . Но на самом деле это очень удобно, потому что он сбрасывает объект тестирования и повторно инициализирует макеты. Вы можете использовать это например
- Если у вас есть различные методы тестирования, использующие одни и те же аннотированные экземпляры, чтобы гарантировать, что различные тестовые прогоны не используют одинаковое записанное поведение
- Когда используются повторяющиеся / параметризованные тесты. Например, вы можете включить этот вызов в сам метод теста и получить шпионский объект в качестве параметра теста (как часть теста). Эта способность очень
@DataProviderв сочетании с@DataProviderTestNG@DataProvider(об этом мы расскажем в другом сообщении в блоге).
@Spy аннотированный объект может быть создан двумя способами
- Автоматически с помощью инфраструктуры Mockito, если есть конструктор по умолчанию (не параметризованный)
- Или явно инициализирован (например, когда есть только конструктор не по умолчанию)
Объект тестирования, аннотированный @InjectMocks также можно явно инициализировать.