РЕДАКТИРОВАТЬ: полевые инъекции широко рассматриваются (включая меня сейчас), как плохая практика. Читайте здесь для получения дополнительной информации . Я бы предложил использовать инжектор конструктора вместо этого.
Внедрение зависимостей — очень мощная особенность контейнеров Inversion of Control, таких как Spring и EJB. Всегда полезно инкапсулировать введенные значения в частные поля. Но инкапсуляция автопроводных полей снижает тестируемость.
Мне нравится, как Mockito решил эту проблему, чтобы смоделировать поля с автопроводкой. Поясню это на примере. (В этом сообщении ожидается, что вы немного знакомы с синтаксисом Mockito, но он достаточно информативен.)
Вот первая зависимость модуля тестирования. Это бобы Синглтон-Спринг. Этот класс будет издеваться в тесте.
@Repository
public class OrderDao {
public Order getOrder(int irderId){
throw new UnsupportedOperationException("Fail is not mocked!");
}
}
Вот вторая зависимость класса тестирования. Это также компонент Spring. Этот класс будет шпионить (частично издеваться) в тесте. Его метод calculatePriceForOrderбудет вызываться без изменений. Второй метод будет заглушен.
@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;
}
}
И вот класс под тестом. Это автоматически связывает зависимости выше.
@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— Создает экземпляр объекта тестирования и пытается внедрить поля, помеченные@Mockили@Spyзакрытые полями объекта тестирования.@Mock— Создает фиктивный экземпляр поля, которое он аннотирует@Spy— Создает шпион для аннотированного поля
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)вызов ненужными накладными расходами. Но на самом деле это очень удобно, потому что он сбрасывает объект тестирования и повторно инициализирует макеты. Вы можете использовать это например
- Если у вас есть различные методы тестирования, использующие одни и те же аннотированные экземпляры, чтобы гарантировать, что различные тестовые прогоны не используют одинаковое записанное поведение
- Когда используются повторяющиеся / параметризованные тесты. Например, вы можете включить этот вызов в сам метод теста и получить шпионский объект в качестве параметра теста (как часть теста). Эта способность очень сексуальна в сочетании с
@DataProviderфункцией TestNG (это будет объяснено в другом сообщении в блоге).
@Spy Аннотированный объект может быть создан двумя способами
- Автоматически с помощью инфраструктуры Mockito, если есть конструктор по умолчанию (не параметризованный)
- Или явно инициализирован (например, когда есть только конструктор не по умолчанию)
Аннотируемый объект тестирования также @InjectMocksможет быть явно инициализирован.