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