How to use a real mapstruct mapper method in test? - java

I have a class that uses mapper in one of the methods to return the result as a list of DTOs.
I want to use a real mapper method as it gives me possibility to check real functionality.
It's my test class:
#ExtendWith(MockitoExtension.class)
public class UserCardServiceTest {
#Mock
private MyCardRepository myCardRepository;
#Mock(answer = Answers.CALLS_REAL_METHODS)
private MyMapper myMapper;
#InjectMocks
private MyServiceImpl underTest;
#BeforeEach
void setUp() {
listOfExpiredUserCardEntities = new ArrayList<>(List.of(
getValidUserCardEntity()
.setExpirationDate(LocalDate.now().minusDays(1))
.setBlocked(false),
getValidUserCardEntity()
.setExpirationDate(LocalDate.now().minusWeeks(1))
.setBlocked(false),
getValidUserCardEntity()
.setExpirationDate(LocalDate.now().minusYears(1))
.setBlocked(false),
getValidUserCardEntity()
.setExpirationDate(LocalDate.now().minusMonths(1))
.setBlocked(false));
}
#Test
void shouldChangeStatusIfCardExpired() {
given(myCardRepository.findAllByUserId(1L))
.willReturn(listOfCardEntities);
assertThat(underTest.getActiveUserCardsById(1L)).isNotNull().hasSize(4);
assertThat(underTest.getActiveUserCardsById(1))
.extracting(CardDto::isBlocked)
.containsExactly(true, true, true, true)
.doesNotContainNull();
}
My method to test:
#RequiredArgsConstructor
public class MyServiceImpl implements MyCardService {
private final MyCardRepository myCardRepository;
private final MyMapper myMapper;
#Override
public List<CardDto> getActiveUserCardsById(final long userId) {
List<CardEntity> listOfEntities = myRepository.findAllByUserId(userId);
listOfEntities.stream().filter(validationService::isCardExpired)
.forEach(userCard -> userCard.setBlocked(true));
List<CardEntity> sorted = listOfEntities.stream()
.sorted(o -> o.getName().toUpperCase())
.toList();
return myMapper
.listOfCardEntitiesToCardDto(sorted);
}
But as a result I get:
java.lang.AssertionError:
Expected size: 4 but was: 0 in:
[]
Can anyone say how to invoke a real mapstruct mapper method, but not prepared answer.
Is it possible to do with only #ExtendWith(MockitoExtension.class)?

I had the same problem in my projects. I am using the following solution:
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
.....
#Mock
private MyCardRepository myCardRepository;
#Spy
private MyMapper myMapper = Mappers.get(MyMapper.class);
#InjectMocks
private MyServiceImpl underTest;
This works with only #ExtendWith(MockitoExtension.class). There is no need to start the entire SpringBoot Context ;)

Related

#Component always null in spring boot

I have two classes which are annotated as #Component
#Component
public class ClientMapper {
public Client convert(ClientEntity clientEntity) {
Client client = new Client();
BeanUtils.copyProperties(clientEntity, client);
return client;
}
public ClientEntity convert(Client client) {
ClientEntity clientEntity = new ClientEntity();
BeanUtils.copyProperties(client, clientEntity);
return clientEntity;
}
}
#Component
public class OrderMapper {
public Order convert(OrderEntity orderEntity) {
Order order = new Order();
BeanUtils.copyProperties(orderEntity, order);
return order;
}
public OrderEntity convert(Order order) {
OrderEntity orderEntity = new OrderEntity();
BeanUtils.copyProperties(order, orderEntity);
return orderEntity;
}
}
I injected them into different services
#Service
#AllArgsConstructor
public class ClientServiceImpl implements ClientService {
private final ClientMapper clientMapper;
private final ClientRepository clientRepository;
#Service
#AllArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderMapper orderMapper;
private final OrderRepository orderRepository;
private final OrderNumberRepository orderNumberRepository;
But all time my mappers is null. I don't create new Object of them using new command. Also with my repository interfaces everything is fine, so my way to inject my comments(#AllArgsContrustor) works correct.
Little note, I have tests classes where I used #InjectMocks on my services classes. Can it be that my error occupied because of this annotation?
#ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
#Mock
private OrderRepository orderRepository;
#InjectMocks
private OrderServiceImpl orderService;
You are using MockitoExtension, spring won't create component of OrderMapper.
If you need actual implementation. Use #Spy annotation of MockitoExtension.
#ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
#Mock
private OrderRepository orderRepository;
#Spy
private OrderMapper orderMapper = new OrderMapper(); // Note: new OrderMapper() is optional as you have No Argument Constructor
#InjectMocks
private OrderServiceImpl orderService;
Or as a practice #Mock is always best way to go instead of #Spy incase of Unit test.

How to Test void Method with spring repository using Junit and Mockito

i have a java spring service that call a spring data repository and i want to do a Junit test with mockito , this is my class and the service that i want to test :
#Service
public class DataServiceImpl implements DataService{
#Autowired
private CautionRepository cautionRepository;
#Override
public void addCautions(List<CautionsDTO> cautions, Contrat contrat) {
if(cautions != null && !cautions.isEmpty()) {
cautions.forEach(caution -> {
Caution caution = new Caution();
cautionContrat.setAmount(caution.getAmount());
cautionContrat.setDate(caution.getDate());
caution.setContrat(contrat);
cautionRepository.save(caution);
});
}
}
}
and this is my Unit test
#RunWith(SpringRunner.class)
public class DataServiceImplTest{
#InjectMocks
private DataServiceImpl dataService;
#Mock
private CautionRepository cautionRepository;
#Test
public void addListCautionsTest() {
List<CautionsDTO> cautions = new ArrayList<>();
ContratExportation contrat = new ContratExportation();
Caution caution = new Caution();
dataDelService.addCautions(cautions,contrat);
Mockito.verify(cautionRepository, times(1)).save(caution);
}
}
When i run the test i got the folowwing error :
Wanted but not invoked:
cautionRepository.save(
org.model.Caution#2abe9173
);
-> at org.service.DataServiceImplTest.addListCautionsTest(DataServiceImplTest.java:292)
Actually, there were zero interactions with this mock.
Do you have any idea please what is the mistake with my test
You never add a value to cautions so the loop is not executed and verify must fail.
Add a value to the list and the test should pass:
List<CautionsDTO> cautions = new ArrayList<>();
ContratExportation contrat = new ContratExportation();
CautionDTO caution = new CautionDTO();
cautions.add(caution);
dataDelService.addCautions(cautions,contrat);
This should be the correct code for executing the test
#RunWith(SpringRunner.class)
public class DataServiceImplTest{
#Autowired
private DataServiceImpl dataService;
#MockBean
private CautionRepository cautionRepository;
#Test
public void addListCautionsTest() {
List<CautionsDTO> cautions = new ArrayList<>();
ContratExportation contrat = new ContratExportation();
Caution caution = new Caution();
dataDelService.addCautions(cautions,contrat);
Mockito.verify(cautionRepository, times(1)).save(caution);
}
}
First of all, you forgot to add caution object into cautions list. But other than that you are mixing Unit test with Integration test.
You need to annotate your test class with #RunWith(MockitoJUnitRunner.class),
or
You need to annotate your mock objects with #MockBean and add #Autowired to your test class DataServiceImpl object.
Now, let me explain to you.
Unit Test
When you want to write a unit test, you should not make use of application context (autowiring).
By the way, a better approach is to annotate your DataServiceImpl with #RequiredArgsConstructor from Lombok and remove #Autowired from CautionRepository. This will allow you to instantiate DataServiceImpl in a setup method in your unit test.
Your DataServiceImpl class should be:
#Service
#RequiredArgsConstructor
public class DataServiceImpl implements DataService{
private final CautionRepository cautionRepository;
#Override
public void addCautions(List<CautionsDTO> cautions, Contrat contrat) {
// your code...
}
}
and your new unit test class:
#RunWith(MockitoJUnitRunner.class)
public class DataServiceImplTest{
private DataServiceImpl dataService;
#Mock
private CautionRepository cautionRepository;
#Before
public void setup() {
dataService = new DataServiceImpl(cautionsRepository);
}
#Test
public void addListCautionsTest() {
// your test code...
}
}
Integration Test
Now, if you want to create an integration test, use #RunWith(SpringRunner.class). By doing this your application context will be loaded. In your case you can create a mocked bean inside your context by annotating your object with #MockBean. This will inject mocked object into your context and it will get auto wired in your real class.
For this your new DataServiceImpl class can remain same as above. But change your integration test into:
#RunWith(SpringRunner.class)
public class DataServiceImplTest{
#Autowired
private DataServiceImpl dataService;
#MockBean // it will be injected automatically
private CautionRepository cautionRepository;
#Test
public void addListCautionsTest() {
// your test code...
}
}
Hope, you now understand the difference and the mistake you were doing :)

Why isn't passed into Mockito.when not used?

I think there is either a basic misunderstanding on my part with how when works, or more specifically how Mockito is working.
I have a service class that has a utility class injected via constructor. The utility class has some other dependencies autowired by constructor as well.
A service class method calls several methods in the utility class. The test uses when/thenReturn statements on the called utility methods. When I make a call on the service method, I get an NPE on a utility method called with a null parameter. But I expected parameters set in the when clause to be set. Code below:
#Service
public class ServiceClass {
private Utility utility;
public ServiceClass(Utility utility) {
this.utility = utility;
}
public serviceMethod(MyDocument myDocument, List<Attachment> attachments) {
SomeType variable1;
OtherType variable2;
List<String> stringList;
long time;
time = utility.method1(variable1, variable2);
stringList = utility.method2(myDocument, attachments.get(0));
...
}
#Service
public class Utility {
private Dependency1 depend1;
private Dependency2 depend2;
public Utility(Dependency1 depend1, Dependency2 depend2) {
this.depend1 = depend1;
this.depend2 = depend2;
}
public long method1(SomeType var1, OtherType var2) {
....
}
public List<String> method2(MyDocument myDoc, Attachment attach) {
....
}
Now the test code looks as follows:
public TestClass {
private ServiceClass serviceClass;
#Mock
private Depend1 depend1;
#Mock
private Depend2 depend2;
#InjectMocks
private Utility utility;
#Rule
public MockitoRule rule = MockitoJUnit.rule();
#Before
public void setup() {
serviceClass = new ServiceClass(utility);
}
#Test
public testServiceMethod() {
long time = System.currentTimeMillis();
MyDocument doc = new MyDocument();
List<Attachments> attachments = Arrays.asList(new Attachment(...), new Attachment(...));
SomeType some = new SomeType();
OtherType other = new OtherType();
when(utility.method1(some, other)).thenReturn(time);
when(utility.method2(doc, attachments.get(0)).thenReturn(Arrays.asList(new String("stg 1"), new String("stg 2"));
String resp = serviceClass.serviceMethod(doc, attachments);
assertEquals("service completed", resp);
}
}
But when utility.method2 is called, myDocument shows as null. I was expecting that it would be an instance of MyDocument.
Do I have something misconfigured? Am I missing a concept here? All help appreciated!
Thanks.
UPDATE
Corrected the arguments to the serviceMethod.
The ServiceClass is the class you are testing, so you should anotate with #Mock only the dependencies of this class in your test, in your case the utility attribute, remove Depend1 and Depend1 declarations. The setup method is not necessary, you can anotate serviceClass in your test with #InjectMocks instead, it take cares of the injections automatically. And finally, your TestClass need the #RunWith(MockitoJunitRunner.class) to make everything work if it's not there.
#RunWith(MockitoJunitRunner.class)
public class TestClass{
#InjectMocks
private ServiceClass serviceClass;
#Mock
private Utility utility;
}
This is a basic definition for your TestClass, the test itself looks correct, but can be improved to use ArgumentMatchers on the "when" clause and add a verify clause using ArgumentCaptor to validate the parameters.

Injecting Spring Boot component with Mockito mocks

Here is my GitHub repo for reproducing the exact issue.
Not sure if this is a Spring Boot question or a Mockito question.
I have the following Spring Boot #Component class:
#Component
class StartupListener implements ApplicationListener<ContextRefreshedEvent>, KernelConstants {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// Do stuff involving 'fizz' and 'buzz'
}
}
So StartupListener has no constructor and is intentionally a Spring #Component that gets its properties injected via #Autowired.
The #Configuration class providing these dependencies is here, for good measure:
#Configuration
public class MyAppConfiguration {
#Bean
public Fizz fizz() {
return new Fizz("OF COURSE");
}
#Bean
public Buzz buzz() {
return new Buzz(1, true, Foo.Bar);
}
}
I am now trying to write a JUnit unit test for StartupListener, and I have been using Mockito with great success. I would like to create a mock Fizz and Buzz instance and inject StartupListener with them, but I'm not sure how:
public class StartupListenerTest {
private StartupListener startupListener;
#Mock
private Fizz fizz;
#Mock
price Buzz buzz;
#Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
// Doesn't matter what I'm testing here, the point is I'd like 'fizz' and 'buzz' to be mockable mocks
// WITHOUT having to add setter methods to StartupListener and calling them from inside test code!
}
}
Any ideas as to how I can accomplish this?
Update
Please see my GitHub repo for reproducing this exact issue.
You can use #SpyBeaninstead of #MockBean, SpyBean wraps the real bean but allows you to verify method invocation and mock individual methods without affecting any other method of the real bean.
#SpyBean
private Fizz fizz;
#SpyBean
price Buzz buzz;
You can use #MockBean to mock beans in ApplicationContext
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
If no bean of the same type is defined, a new one will be added. This annotation is useful in integration tests where a particular bean – for example, an external service – needs to be mocked.
To use this annotation, we have to use SpringRunner to run the test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MockBeanAnnotationIntegrationTest {
#MockBean
private Fizz fizz;
}
And i will also suggest to use #SpringBootTest
The #SpringBootTest annotation tells Spring Boot to go and look for a main configuration class (one with #SpringBootApplication for instance), and use that to start a Spring application context.
you can do something likewise,
#RunWith(MockitoJUnitRunner.class)
public class StartupListenerTest {
#Mock
private Fizz fizz;
#Mock
price Buzz buzz;
#InjectMocks
private StartupListener startupListener;
#Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
....
}
}
Here is a simple example that just uses plain Spring.
package com.stackoverflow.q54318731;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
#SuppressWarnings("javadoc")
public class Answer {
/** The Constant SPRING_CLASS_RULE. */
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
/** The spring method rule. */
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
static final AtomicInteger FIZZ_RESULT_HOLDER = new AtomicInteger(0);
static final int FIZZ_RESULT = 43;
static final AtomicInteger BUZZ_RESULT_HOLDER = new AtomicInteger(0);;
static final int BUZZ_RESULT = 42;
#Autowired
ConfigurableApplicationContext configurableApplicationContext;
#Test
public void test() throws InterruptedException {
this.configurableApplicationContext
.publishEvent(new ContextRefreshedEvent(this.configurableApplicationContext));
// wait for it
TimeUnit.MILLISECONDS.sleep(1);
assertEquals(FIZZ_RESULT, FIZZ_RESULT_HOLDER.get());
assertEquals(BUZZ_RESULT, BUZZ_RESULT_HOLDER.get());
}
#Configuration
#ComponentScan //so we can pick up the StartupListener
static class Config {
final Fizz fizz = Mockito.mock(Fizz.class);
final Buzz buzz = Mockito.mock(Buzz.class);
#Bean
Fizz fizz() {
Mockito.when(this.fizz.calculateSomething())
.thenReturn(FIZZ_RESULT);
return this.fizz;
}
#Bean
Buzz buzz() {
Mockito.when(this.buzz.calculateSomethingElse())
.thenReturn(BUZZ_RESULT);
return this.buzz;
}
}
#Component
static class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
FIZZ_RESULT_HOLDER.set(this.fizz.calculateSomething());
BUZZ_RESULT_HOLDER.set(this.buzz.calculateSomethingElse());
}
}
static class Fizz {
int calculateSomething() {
return 0;
}
}
static class Buzz {
int calculateSomethingElse() {
return 0;
}
}
}
If you modify your StartupListenerTest to just focus on the StartupListener class
i.e. add the class to the SpringBootTest annotation
#SpringBootTest(classes= {StartupListener.class})
You will get a different error, but it's more focused on the class you're trying to test.
onApplicationEvent method will fire before the test runs. This means you won't have initialized your mock with when(troubleshootingConfig.getMachine()).thenReturn(machine); and so there's no Machine returned when getMachine() is called, hence the NPE.
The best approach to fix this really depends on what you're trying to achieve from the test. I would use an application-test.properties file to set up the TroubleShootingConfig rather than use an #MockBean. If all you're doing in your onApplicationEvent is logging then you could use #SpyBean as suggested in another answer to this question. Here's how you could do it.
Add an application-test.properties to resources folder so it's on the classpath:
troubleshooting.maxChildRestarts=4
troubleshooting.machine.id=machine-id
troubleshooting.machine.key=machine-key
Add #Configuration to TroubleshootingConfig
#Configuration
#ConfigurationProperties(prefix = "troubleshooting")
public class TroubleshootingConfig {
private Machine machine;
private Integer maxChildRestarts;
... rest of the class
Change StartupListenerTest to focus on the classes your testing and spy on the TroubleshootingConfig. You also need to #EnableConfigurationProperties
#RunWith(SpringRunner.class)
#SpringBootTest(classes= {TroubleshootingConfig.class, StartupListener.class})
#EnableConfigurationProperties
public class StartupListenerTest {
#Autowired
private StartupListener startupListener;
#SpyBean
private TroubleshootingConfig troubleshootingConfig;
#MockBean
private Fizzbuzz fizzbuzz;
#Mock
private TroubleshootingConfig.Machine machine;
#Mock
private ContextRefreshedEvent event;
#Test
public void should_do_something() {
when(troubleshootingConfig.getMachine()).thenReturn(machine);
when(fizzbuzz.getFoobarId()).thenReturn(2L);
when(machine.getKey()).thenReturn("FLIM FLAM!");
// when
startupListener.onApplicationEvent(event);
// then
verify(machine).getKey();
}
}

Injecting DozerBeanMapper with Mockito

I'm using Dozer in my Spring services. How to inject a DozerBeanMapper into a tested service using JUnit and Mockito?
My java class (if simplified) looks like:
#Service
public class UnicornService {
private final DozerBeanMapper dozer;
#Autowired
public UnicornService(DozerBeanMapper dozer) {
this.dozer = dozer;
}
public UnicornDto convert(Unicorn unicorn) {
return dozer.map(unicorn, UnicornDto.class);
}
}
A test class using JUnit 4 + Mockito + Hamcrest looks like:
import static com.shazam.shazamcrest.MatcherAssert.assertThat;
import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
#RunWith(MockitoJUnitRunner.class)
public class UnicornServiceTest {
#Mock
private DozerBeanMapper dozer;
#InjectMocks
private UnicornService unicornService;
#Test
public void testConvert() throws Exception {
final Unicorn original = ...
final UnicornDto expected = ...
// Execute the method being tested
final UnicornDto result = unicornService.convert(original);
// Validation
assertThat(result, sameBeanAs(expected));
}
}
The problem is that a mocked Dozer instance is not mapping objects as expected - by default, Mockito stubs return empty or null objects. And if I remove #Mock annotation from the test, it throws NPE!
Use #Spy annotation on DozerBeanMapper object. This will allow you to call all the normal methods of the object while still this object is managed by Mockito (as a mock) and injected into a tested service.
#RunWith(MockitoJUnitRunner.class)
public class UnicornServiceTest {
#Spy
private DozerBeanMapper dozer;
#InjectMocks
private UnicornService unicornService;
// ...
Another solution that I've found is to refactor your code. It seems less attractive to me, because it makes more harm then good, just in the sake of writing tests.
Use injection via setter in the service
#Service
public class UnicornService {
private DozerBeanMapper dozer;
#Autowired
public setDozer(DozerBeanMapper dozer) {
this.dozer = dozer;
}
public UnicornDto convert(Unicorn unicorn) {
return dozer.map(unicorn, UnicornDto.class);
}
}
Refactored test:
#RunWith(MockitoJUnitRunner.class)
public class UnicornServiceTest {
#InjectMocks
private UnicornService unicornService;
#Before
public void injectDozer() {
final DozerBeanMapper dozer = new DozerBeanMapper();
unicornService.setDozer(dozer);
}
// ...
You should not be relying on the mapper creating a proper object at all, just that the service calls the mapper and returns its result. The actual mapping should be tested in a unit test for the mapper. Ie
#RunWith(MockitoJUnitRunner.class)
public class UnicornServiceTest {
#Mock
private DozerBeanMapper dozer;
#InjectMocks
private UnicornService unicornService;
#Test
public void testConvert() throws Exception {
final Unicorn original = mock(Unicorn.class);
final UnicornDto expected = mock(UnicornDto.class);
when(dozer.map(original, UnicornDto.class)).thenReturn(expected);
// Execute the method being tested
final UnicornDto result = unicornService.convert(original);
// Validate that the call was delegated to the mapper
assertThat(result, is(expected));
}
}

Categories

Resources