Mocking autowired dependencies with Mockito - spring

I'm writing unit tests for a Spring project with Junit 5 and Mockito 4.
I have to test a class that takes 2 objects via constructor and other 2 via #Autowired. I need to mock those 4 objects, so I annotated them with #Mock in my test class and then annotated the tested class with #InjectMocks.
I thought that #InjectMocks would inject my 4 mocks into myService, but it's only injecting the 2 that are passed by constructor, while the other 2 are null.
I'm looking for a solution that doesn't implies changes in the tested service.
The tested class looks like this:
#Service
public class MyService {
private String key = "KEY";
#Autowired
private FirstApiWrapper firstApiWrapper;
#Autowired
private SecondApiWrapper secondApiWrapper;
private MyRepository myRepository;
private OtherService otherService;
#Autowired
public MyService(
MyRepository myRepository,
OtherService otherService
) {
super();
this.myRepository = myRepository;
this.otherService = otherService;
}
My test class looks like this:
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
MyRepository myRepository;
#Mock
OtherService otherService;
#Mock
FirstApiWrapper firstApiWrapper;
#Mock
SecondApiWrapper secondApiWrapper;
#InjectMocks
MyService myService;
Any ideas of what is wrong with my code?
Thank you all very much!
-- I've also tried something based on this question:
#Mock
FirstApiWrapper firstApiWrapper;
#Mock
SecondApiWrapper secondApiWrapper;
#InjectMocks
MyService myService;
#BeforeEach
private void setUp() {
myService = new MyService(
Mockito.mock(MyRepository.class),
Mockito.mock(OtherService.class)
);
}
But the result is exactly the same. Also, if I delete repository and service instances and try to inject only the wrappers, It still fails!

I found a way to solve it without rewriting the existing code, by adding this to the test class:
#BeforeEach
private void setUp() {
MockitoAnnotations.openMocks(this);
}
But I'm not sure if it is a "correct" way of doing it.

Once of the issues with fields autowiring is that mockito won't be able to inject anything. So why do you need to mix the styles of injection if you already have a constructor-injection?
Rewrite your class:
#Service
public class MyService {
private String key = "KEY";
private final MyRepository myRepository;
private final OtherService otherService;
private final FirstApiWrapper firstApiWrapper;
private final SecondApiWrapper secondApiWrapper;
#Autowired // if its the only constructor in the class, you can omit #Autowired, spring will be able to call it anyway. You can even use Lombok to generate this constructor for, so you won't need to even write this method
public MyService(
MyRepository myRepository,
OtherService otherService,
FirstApiWrapper firstApiWrapper,
SecondApiWrapper secondApiWrapper
) {
this.myRepository = myRepository;
this.otherService = otherService;
this.firstApiWrapper = firstApiWrapper;
this.secondApiWrapper = secondApiWrapper;
}
With this design you can safely use #Mock / #InjectMocks annotations in the test
Mockito will create the instance of the class and inject relevant mocks.

Related

Mock bean is autowired into #Spy bean mockito

My unit test try #Spy beanA. But BeanA #autowire bean B as bellow:
#RunWith(SpringRunner.class)
public class MyServiceImplTest {
#Spy
private BeanA beanA;
#InjectMocks
private MyService myService = new MyServiceImpl();
#Test
public void testDoSomeThing(){
myService.doSomeThing();
}
}
MyServiceImpl as bellow :
#Service
public class MyServiceImpl {
#Autowired
private BeanA beanA;
public doSomeThing(){
....
beanA.beanADoSomeThing()
....
}
}
beanA as bellow
#Service
public class BeanA {
#Autowired
private BeanB beanB;
public beanADoSomeThing(){
...
//Null pointer exception in here because beanB=null
beanB.beanBDoSomeThing()
}
}
when run unit test i get null pointer exception at line beanB.beanBDoSomeThing(), I can understand the reason but how to resolve this issue?
I have tried
#Mock
private BeanB beanB;
But this not work, how to resolve this issue ?
If you want to spy your bean in context, you need #SpyBean annotation instead of #Spy and also you should autowire your service to be tested, smth like this:
#SpringBootTest
#RunWith(SpringRunner.class)
public class ExTest {
#SpyBean
private BeanA beanA;
#Autowired
private MyService myService;
#Test
public void testDoSomeThing() {
myService.doSomeThing();
}
}
If you don't want to load application context and test only MyServiceImpl behavior in isolation, you can use pure Mockito and mock or spy dependencies of MyServiceImpl:
#RunWith(MockitoJUnitRunner.class)
public class MockitTest {
#InjectMocks
private MyServiceImpl myService;
#Mock
private BeanA beanA;
#Test(expected = RuntimeException.class)
public void test() {
doThrow(new RuntimeException()).when(beanA).beanADoSomeThing();
myService.doSomeThing();
}
}

Mockito injecting mocks Spring Boot test

Hi I have a service class that contains mapper and repository:
#Service
public class ProductServiceImp implements ProductService {
#Autowired
private ProductRepository repository;
#Autowired
private WarehouseApiMapper mapper;
public ProductServiceImp(ProductRepository repository) {
this.repository = repository;
}
}
Repository:
#Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
Mapper:
#Mapper(componentModel = "spring")
public interface WarehouseApiMapper {
WarehouseApiMapper mapper = Mappers.getMapper(WarehouseApiMapper.class);
Product ProductDtoToProduct(ProductDto productDto);
ProductDto ProductToProductDto(Product product);
}
In test class I would like to inject mock repository and autowired mapper
Here is my test class:
#SpringBootTest
public class ProductServiceTest {
#Mock
ProductRepository repository;
#InjectMocks
ProductServiceImp service;
#ParameterizedTest
#MethodSource("provideParametersProductUpdate")
void assert_that_product_is_updated_correctly(String productName, BigDecimal productPrice) {
Product oldProduct = new Product("Product that does not exist", BigDecimal.valueOf(1000000), null);
oldProduct.setId(1);
Mockito.when(repository.findById(1)).thenReturn(Optional.of(oldProduct));
Product newProduct = new Product(productName, productPrice, null);
newProduct.setId(1);
ProductDto updatedProduct = service.updateProduct(newProduct);
Assertions.assertEquals(productPrice, updatedProduct.getPrice());
Assertions.assertEquals(productName, updatedProduct.getName());
}
private static Stream<Arguments> provideParametersProductUpdate() {
return Stream.of(
Arguments.of("dark chocolate", BigDecimal.valueOf(3.2)),
Arguments.of("chewing gum", BigDecimal.valueOf(1.2)),
Arguments.of("lollipop", BigDecimal.valueOf(4.0))
);
}
}
Code throws NullPointerException when is trying to map object in service method.
Somebody knows how can I inject this? Thanks for ur answers
If you want to create just a Mockito test you could use the annotation #RunWith(MockitoJUnitRunner.class) instead of #SpringBootTest.
But if you want to create a Spring Boot integration test then you should use #MockBean instead of #Mock and #Autowired instead of #InjectMocks.

MapStruct mapper not initialized with autowired when debug

I use spring boot 2.3.2 with mapstruct.
In a service class I have a mapper who have an autowired annotation.
#Service
public BillingService{
private BillingRepository billingRepository;
#Autowired
private BillingMapper billingMapper;
#Autowired
public BillingService (BillingRepository billingRepository){
this.billingRepository=billingRepository;
}
public void getBilling(Long billingId){
....
billingMapper.convertTo(...);
}
}
#RunWith(MockitoJUnitRunner.class)
public class BillingServiceTest{
#Mock
BillingRepository billingRepository;
private BillingService bilingService;
#Spy
private BillingMapper billingMapper = Mappers.getMapper(BillingMapper.class);
#BeforeEach
public void setup(){
MockitoAnnotations.initMocks(this);
billingService = new BillingService();
}
#Test
public void testGetBilling(){
List<Billing> billings = new ArrayList<>();
...
List<BillingPayload> payloads = new ArrayList<>();
when(billingMapper.convertTo(billings)).thenReturn(payloads);
bilingService.getBilling(1l);
}
}
#Mapper(componentModel="spring")
public interface BillingMapper{
...
}
When I debug and I'm stuck in getBilling method in BillingService Class, billingMapper is alway null;
You are using very strange configuration. First of all you have method returning BillingService that doesn't specify any return value so this would not even compile. I suggest following way:
#Service
public BillingService{
private final BillingRepository billingRepository;
private final BillingMapper billingMapper;
// constructor with bean injection
public BillingService(final BillingRepository billingRepository,
final BillingMapper billingMapper) {
this.billingRepository = billingRepository;
this.billingMapper = billingMapper;
}
public void getBilling(Long billingId){
....
billingMapper.convertTo(...);
}
}
Then you can configure your test like following:
#RunWith(SpringJUnit4ClassRunner.class)
public class BillingServiceTest {
#Spy private BillingMapper billingMapper = Mappers.getMapper(BillingMapper.class);
#MockBean private BillingRepository billingRepository;
#Autowired private BillingService billingService;
#TestConfiguration
static class BillingServiceTestContextConfiguration {
#Bean public BillingService billingService() {
return new BillingService(billingRepository, billingMapper);
}
}
#Test
public void testGetBilling(){
List<Billing> billings = new ArrayList<>();
...
List<BillingPayload> payloads = new ArrayList<>();
when(billingRepository.findById(1L)).thenReturn(); // someResult
when(billingMapper.convertTo(billings)).thenReturn(payloads);
bilingService.getBilling(1l);
}
}
#Mapper(componentModel="spring")
public interface BillingMapper{
...
}
Similiar configuration should work. Main problem is that you are mixing #Autowired with setter/constructor injection (don't know since your weird method inside BillingService. Also dont know why you use #Spy annotation when you are tryning to Mock interface. #Spy is used to mock actual instance, while #Mock is mocking Class type. I would stick with #MockBean private BillingMapper billingMapper instead if BillingMapper is specified as Bean in your application.

Injecting one MockBean into another

I have a typical SpringApplication which I am trying to test via MockMvc. The application contains some database calls and some thridparty api calls, and I want to mock all of them, while testing end to end flow, except thirdparty
This is what I have created -
Controller class
public class PortfolioController {
private final PortfolioService portfolioService;
}
Service Class
public class PortfolioService {
private final PortfolioTransactionRepository portfolioTransactionRepository;
private final AlphavantageService alphavantageService;
}
AlphaVantageService
public class AlphavantageService {
private ApiConfig apiConfig;
private final RestTemplate restTemplate;
public Map<String, List<Candle>> getStockQuotes(List<String> symbols) {
return symbols.stream().collect(Collectors.toMap(symbol -> symbol, symbol -> getQuotes(symbol)));
}
}
Now comes the test -
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = PortfolioController.class)
class PortfolioControllerTest {
private List<PortfolioTransaction> transactions;
#MockBean
private AlphavantageService alphavantageService;
#MockBean
private PortfolioService portfolioService;
#Autowired
private PortfolioController portfolioController;
#Autowired
private MockMvc mockMvc;
}
The problem is, when I try to execute any mvc call on server, AlphaVantageService is not injected inside PortfolioService, so till level1, I get the beans injected, but on further levels, I dont get the same.
Is it by design or I am missing something? How should we test such test-cases?
Actually After trying some options here and there, I found a solution.
Just like #MockBean, spring also have a notion called #SpyBean. That solved my problem. So now my test looks like below
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = PortfolioController.class)
#MockBeans(value = {#MockBean(AlphavantageService.class),
#MockBean(PortfolioTransactionRepository.class)})
#SpyBeans(value = {#SpyBean(PortfolioService.class)})
class PortfolioControllerTest {
private List<PortfolioTransaction> transactions;
#Autowired
private AlphavantageService alphavantageService;
#Autowired
#SpyBean
private PortfolioService portfolioService;
#Autowired
private PortfolioController portfolioController;
#Autowired
private MockMvc mockMvc;
}
This works like a charm and I can use full fledged dependency Injection in the tests.

Configuration to use #Mock and #InjectMocks

I want to use a mock DAO to unit test the service layer in my spring application. In the DAO, it's seesinoFactory is injected using #Inject.
When the test class is configured with #RunWith(MockitoJUnitRunner.class)
#RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
#Mock
private MyDao myDaoMock;
#InjectMocks
private MyServiceImpl myService;
}
The output is just as expected.
When I changed the configuration to using #RunWith(SpringJUnit4ClassRunner.class)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "/ServiceTest-context.xml")
public class ServiceTest {
#Mock
private MyDao myDaoMock;
#InjectMocks
private MyServiceImpl myService;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
}
}
The exception,"No qualifying bean of type [org.hibernate.SessionFactory] found for dependency", will be thrown if sessionFactory bean is not available in ServiceTest-context.xml.
What I don't understand is MyDao is annotated with #Mock already, why is sessionFactory still needed?
you must use #RunWith(MockitoJUnitRunner.class) to initialize these mocks and inject them.
#Mock creates a mock.
#InjectMocks creates an instance of the class and injects the mocks that are created with the #Mock or #Spy annotations into this instance.
Or Using Mockito.initMocks(this) as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("test-app-ctx.xml")
public class FooTest {
#Autowired
#InjectMocks
TestTarget sut;
#Mock
Foo mockFoo;
#Before
/* Initialized mocks */
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void someTest() {
// ....
}
}

Resources