@InjectMocks
and @Mock
Consider a new class Store.java
and we want to test the getMaleCashiers()
method:
/* Store.java */
public class Store() {
private String id;
@Autowired
private CashierServiceImpl cashierService;
public Store(String id) {
this.id = id;
}
public List<Cashier> getMaleCashiers() throws CacheException {
// Get list of cashiers by store id from cache or database
List<Cashier> cashierList = cashierService.getByStoreId(this.id);
// Filter and return list of male cashiers
return cashierList
.stream()
.filter(c -> c.getGender().equalsIgnoreCase('male'))
.collect(Collectors.toList()));
}
}
Note that Store
has a private member variable CashierServiceImpl
which is initialized through Spring’s dependency injection framework using @Autowired
.
We start by creating StoreTest.java
which will contain our JUnit test. The test class is annotated with @RunWith(SpringJUnit4ClassRunner.class)
:
/* StoreTest.java */
@RunWith(SpringJUnit4ClassRunner.class)
public class StoreTest() {
// TODO: Initialize objects
// TODO: Write test cases
}
@InjectMocks
and @Mock
We declare the member variables store
and cashierServiceImpl
in the test class.
By annotating store
with @InjectMocks
and cashierServiceImpl
with @Mock
, store
will have a mock instance of cashierServiceImpl
instead of null.
Finally, we also need to call MockitoAnnotations.initMocks(this)
to initialize the mock variables before the test is run. One way to do this is by calling it in a method annotated with @Before
:
/* StoreTest.java */
@RunWith(SpringJUnit4ClassRunner.class)
public class StoreTest() {
@InjectMocks
Store store;
@Mock
CashierServiceImpl cashierService;
@Before
public void initMocks() {
// Initialize mock variables
MockitoAnnotations.initMocks(this);
}
}
Let’s now write the test for the store.getMaleCashiers()
. To recap:
/* Store.java */
public class Store {
// ...
public List<Cashier> getMaleCashiers() throws CacheException {
// Get list of cashiers by store id from cache or database
List<Cashier> cashierList = cashierService.getByStoreId(this.id);
// Return male cashiers
return cashierList
.stream()
.filter(c -> c.getGender().equalsIgnoreCase('male'))
.collect(Collectors.toList()));
}
}
cashierService.getByStoreId(this.id)
retrieves the list of cashiers associated with the given store id from either the cache or database.
For our test on store.getMaleCashers()
:
cashierService.getByStoreId(this.id)
cashierService.getByStoreId(this.id)
does not need to retrieve the result from an actual cache or database when we run the testAs such, we can use Mockito’s when
and thenReturn
methods to mock the result when cashierService.getByStoreId(this.id)
is being called during testing.
What we are concerned with is the following line that filters cashierList
by male cashiers and returns the correct result.
As such, here we have the complete code for our test:
/* StoreTest.java */
@RunWith(SpringJUnit4ClassRunner.class)
public class StoreTest() {
@InjectMocks
Store store;
@Mock
CashierServiceImpl cashierService;
@Before
public void initMocks() {
// Initialize mock variables
MockitoAnnotations.initMocks(this);
}
@Test
public void getMaleCashiersTest_shouldReturnOne() throws CacheException {
// CashierList
List<Cashier> cashierList = new ArrayList<>();
Cashier maleCashier = new Cashier();
maleCashier.setGender("male");
cashierList.add(maleCashier);
Cashier femaleCashier = new Cashier();
femaleCashier.setGender("female");
cashierList.add(femaleCashier);
// Mock getByStoreId(this.id) to return cashierList that we have created
when(cashierService.getByStoreId(any())).thenReturn(cashierList);
// Test and assert
List<Cashier> result = store.getMaleCashiers();
assertEquals(1, result.size());
}
}
Store
instanceIn the above example, we have seen how we can initialize Store using @InjectMocks
and @Mocks
.
There are also other ways to initialize Store
in the test class:
Store
instance@Autowired
on store
member variableStore
instance@RunWith(SpringJUnit4ClassRunner.class)
public void StoreTest() {
Store store;
public StoreTest() {
this.store = new Store("Cheers");
}
// @Test
// ...
}
The issue with manually initializing is that you may not be able to initialize the private member variables of Store
:
Store
does not take in an instance of CashierServiceImpl
in its constructorCashierServiceImpl
is a private member variable which is initialized by Spring’s dependency injection framework using @Autowired
@Autowired
on store
member variableWell, you may then consider to annotate Store
with Autowired
instead and let Spring do its own dependency injection magic thingy. However it works because we don’t really care…do we?
@RunWith(SpringJUnit4ClassRunner.class)
public void StoreTest() {
@Autowired
Store store;
// @Test
// ...
}
Unfortunately, you see errors in your console log when you try to run StoreTest
.
Resolving these errors may or may not be straightforward, depending on how complex the member variables are. You will need to declare @ComponentScan so that the autowired variables can be initialized when the test is run.
On top of that, component scanning can result in longer compile time when running the test file, especially for a large codebase.