РЕДАКТИРОВАТЬ: полевые инъекции широко рассматриваются (включая меня сейчас), как плохая практика. Читайте здесь для получения дополнительной информации . Я бы предложил использовать инжектор конструктора вместо этого.
Внедрение зависимостей — очень мощная особенность контейнеров 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
может быть явно инициализирован.