У чому різниця між @Mock
і @InjectMocks
в рамках Mockito?
У чому різниця між @Mock
і @InjectMocks
в рамках Mockito?
Відповіді:
@Mock
створює макет. @InjectMocks
створює екземпляр класу та вводить у цей екземпляр макети, створені за допомогою @Mock
(або @Spy
) приміток.
Зауважте, що ви повинні використовувати @RunWith(MockitoJUnitRunner.class)
або Mockito.initMocks(this)
ініціалізувати ці макети та вводити їх.
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
//tests...
}
Це зразок коду про те, як @Mock
і як @InjectMocks
працює.
Скажіть, у нас є Game
і Player
клас.
class Game {
private Player player;
public Game(Player player) {
this.player = player;
}
public String attack() {
return "Player attack with: " + player.getWeapon();
}
}
class Player {
private String weapon;
public Player(String weapon) {
this.weapon = weapon;
}
String getWeapon() {
return weapon;
}
}
Як бачите, Game
класу потрібно Player
виконати attack
.
@RunWith(MockitoJUnitRunner.class)
class GameTest {
@Mock
Player player;
@InjectMocks
Game game;
@Test
public void attackWithSwordTest() throws Exception {
Mockito.when(player.getWeapon()).thenReturn("Sword");
assertEquals("Player attack with: Sword", game.attack());
}
}
Mockito знущається з класу Player та його поведінки з використанням when
та thenReturn
методом. Нарешті, використання @InjectMocks
Mockito поставить це Player
в Game
.
Зверніть увагу, що вам навіть не потрібно створювати new Game
об’єкт. Мокіто буде вводити його вам.
// you don't have to do this
Game game = new Game(player);
Ми також отримаємо таку саму поведінку, використовуючи @Spy
анотацію. Навіть якщо назва атрибута інша.
@RunWith(MockitoJUnitRunner.class)
public class GameTest {
@Mock Player player;
@Spy List<String> enemies = new ArrayList<>();
@InjectMocks Game game;
@Test public void attackWithSwordTest() throws Exception {
Mockito.when(player.getWeapon()).thenReturn("Sword");
enemies.add("Dragon");
enemies.add("Orc");
assertEquals(2, game.numberOfEnemies());
assertEquals("Player attack with: Sword", game.attack());
}
}
class Game {
private Player player;
private List<String> opponents;
public Game(Player player, List<String> opponents) {
this.player = player;
this.opponents = opponents;
}
public int numberOfEnemies() {
return opponents.size();
}
// ...
Це тому, що Mockito перевірить Type Signature
клас гри, який є Player
і List<String>
.
У вашому тестовому класі тестований клас слід зазначити @InjectMocks
. Це говорить Mockito, в який клас вводити макети:
@InjectMocks
private SomeManager someManager;
З цього моменту ми можемо вказати, які конкретні методи чи об’єкти всередині класу, у цьому випадку SomeManager
, будуть замінені макетами:
@Mock
private SomeDependency someDependency;
У цьому прикладі SomeDependency
всередині SomeManager
класу буде знущатися.
@Mock
анотація висміює відповідний об’єкт.
@InjectMocks
анотація дозволяє вставити в базовий об'єкт різні (і відповідні) макети, створені @Mock
.
Обидва доповнюють один одного.
@InjectMocks
для побудови цього класу і шпигував за ним.
Наприклад
@Mock
StudentDao studentDao;
@InjectMocks
StudentService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
Тут нам потрібен клас DAO для класу обслуговування. Отже, ми знущаємось над ним і вводимо його в екземпляр класу обслуговування. Так само у весняній рамці всі @Autowired боби можна знущатись від @Mock в jUnits та вводити їх у квасолю через @InjectMocks.
MockitoAnnotations.initMocks(this)
метод ініціалізує ці макети та вводить їх для кожного методу тестування, тому його потрібно викликати в setUp()
методі.
"Сміяльна рамка", на якій спирається Mockito, - це рамка, яка дає вам можливість створювати Mock об'єкти (по-старому, ці об'єкти можна назвати шунтами, оскільки вони працюють як шунти для залежних від функціональності) Іншими словами, макет Об'єкт використовується для імітації реального об'єкта, від якого залежить ваш код, ви створюєте проксі-об'єкт із глузливою рамкою. Використовуючи макетні об’єкти у своїх тестах, ви фактично переходите від звичайного одиничного тестування до інтеграційного тестування
Mockito - це тестова основа з відкритим кодом для Java, випущена під ліцензією MIT, це "глузлива рамка", яка дозволяє писати прекрасні тести з чистим та простим API. У просторі Java існує багато різних макетних фреймворків, однак, по суті, існує два основні типи фреймворків макетних об'єктів: реалізовані через проксі і ті, які реалізовані за допомогою перейменування класів.
Рамки введення залежностей, такі як Spring, дозволяють вводити свої проксі-об'єкти без зміни коду, макетний об’єкт очікує виклику певного методу, і він поверне очікуваний результат.
@InjectMocks
Анотацію намагається ініціювати об'єкт тестування примірника і впорскують поля анотованих з @Mock
або @Spy
в приватні поля об'єкта тестування.
MockitoAnnotations.initMocks(this)
виклик, скидає тестуючий об’єкт та повторно ініціалізує макети, тому пам’ятайте, що це має у вашій @Before
/ @BeforeMethod
анотації.
Однією з переваг, яку ви отримуєте від підходу, згаданого @Tom, є те, що вам не потрібно створювати будь-які конструктори в SomeManager, а отже, обмежувати клієнтів для його інстанції.
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
//You don't need to instantiate the SomeManager with default contructor at all
//SomeManager someManager = new SomeManager();
//Or SomeManager someManager = new SomeManager(someDependency);
//tests...
}
Чи є це гарною практикою чи ні, залежить від вашої конструкції програми.
Багато людей дали велике пояснення тут про @Mock
проти @InjectMocks
. Мені це подобається, але я думаю, що наші тести та заявки повинні бути написані таким чином, що нам не потрібно було використовувати @InjectMocks
.
Довідка для подальшого читання з прикладами: https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/
@Mock
використовується для оголошення / знущання над посиланнями залежних бобів, тоді @InjectMocks
як використовується для знущання з квасолі, для якої створюється тест.
Наприклад:
public class A{
public class B b;
public void doSomething(){
}
}
тест для класу A
:
public class TestClassA{
@Mocks
public class B b;
@InjectMocks
public class A a;
@Test
public testDoSomething(){
}
}
Анотацію @InjectMocks можна використовувати для автоматичного введення макетних полів у тестовий об’єкт.
Наведений нижче приклад @InjectMocks використовує для введення макету dataMap у бібліотеку даних.
@Mock
Map<String, String> dataMap ;
@InjectMocks
DataLibrary dataLibrary = new DataLibrary();
@Test
public void whenUseInjectMocksAnnotation_() {
Mockito.when(dataMap .get("aData")).thenReturn("aMeaning");
assertEquals("aMeaning", dataLibrary .getMeaning("aData"));
}
Зауважте, що @InjectMocks
це скоро буде застарілим
застарілий @InjectMocks і графік видалення в Mockito 3/4
і ви можете переглядати @avp відповідь та посилання на:
Чому ви не повинні використовувати анотацію InjectMocks для полів автопровідного зв’язку
Хоча вищезгадані відповіді висвітлювались, я просто спробував додати хвилинні деталі, які я бачу відсутні. Причина за ними (The Why).
Ілюстрація:
Sample.java
---------------
public class Sample{
DependencyOne dependencyOne;
DependencyTwo dependencyTwo;
public SampleResponse methodOfSample(){
dependencyOne.methodOne();
dependencyTwo.methodTwo();
...
return sampleResponse;
}
}
SampleTest.java
-----------------------
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class SampleTest{
@InjectMocks
Sample sample;
@Mock
DependencyOne dependencyOne;
@Mock
DependencyTwo dependencyTwo;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
public void sampleMethod1_Test(){
//Arrange the dependencies
DependencyResponse dependencyOneResponse = Mock(sampleResponse.class);
Mockito.doReturn(dependencyOneResponse).when(dependencyOne).methodOne();
DependencyResponse dependencyTwoResponse = Mock(sampleResponse.class);
Mockito.doReturn(dependencyOneResponse).when(dependencyTwo).methodTwo();
//call the method to be tested
SampleResponse sampleResponse = sample.methodOfSample()
//Assert
<assert the SampleResponse here>
}
}