I using MapStruct to map my entities, and I'm mocking my objects using Mockito.
I want to test a method that contains a mapping with mapStruct.
The problem is the nested mapper is always null in my unit tests (works well in the application)
this is my mapper declaration :
#Mapper(componentModel = "spring", uses = MappingUtils.class)
public interface MappingDef {
UserDto userToUserDto(User user)
}
this is my nested mapper
#Mapper(componentModel = "spring")
public interface MappingUtils {
//.... other mapping methods used by userToUserDto
this is the method that I want to test :
#Service
public class SomeClass{
#Autowired
private MappingDef mappingDef;
public UserDto myMethodToTest(){
// doing some business logic here returning a user
// User user = Some Business Logic
return mappingDef.userToUserDto(user)
}
and this is my unit test :
#RunWith(MockitoJUnitRunner.class)
public class NoteServiceTest {
#InjectMocks
private SomeClass someClass;
#Spy
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
#Spy
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
//initMocks is omitted for brevity
#test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
mappingDef is injected correctly, but mappingUtils is always null
Disclamer : this is not a duplicate of this question. He is using #Autowire so he is loading the spring context so he is doing integration tests. I'm doing unit tests, so I dont to use #Autowired
I dont want to make mappingDef and mappingUtils #Mock so I don't need to do when(mappingDef.userToUserDto(user)).thenReturn(userDto) in each use case
If you are willing to use Spring test util, it's fairly easy with org.springframework.test.util.ReflectionTestUtils.
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
...
// Somewhere appropriate
#Before
void before() {
ReflectionTestUtils.setField(
mappingDef,
"mappingUtils",
mappingUtils
)
}
force MapStruct to generate implementations with constructor injection
#Mapper(componentModel = "spring", uses = MappingUtils.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MappingDef {
UserDto userToUserDto(User user)
}
#Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MappingUtils {
//.... other mapping methods used by userToUserDto
use constructor injection, so that you can construct the class under test with a mapper.
#Service
public class SomeClass{
private final MappingDef mappingDef;
#Autowired
public SomeClass(MappingDef mappingDef) {
this.mappingDef = mappingDef;
}
public UserDto myMethodToTest(){
// doing some business logic here returning a user
// User user = Some Business Logic
return mappingDef.userToUserDto(user)
}
Test SomeClass. Note: its not the mapper that you test here, so the mapper can be mocked.
#RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
private SomeClass classUnderTest;
#Mock
private MappingDef mappingDef;
#Before init() {
classUnderTest = new SomeClass(mappingDef);
// defaultMockBehaviour:
when(mappingDef.userToUserDto(anyObject(User.class).thenReturn(new UserDto());
}
#test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
And in a true unit test, test the mapper as well.
#RunWith(MockitoJUnitRunner.class)
public class MappingDefTest {
MappingDef classUnderTest;
#Before
void before() {
// use some reflection to get an implementation
Class aClass = Class.forName( MappingDefImpl.class.getCanonicalName() );
Constructor constructor =
aClass.getConstructor(new Class[]{MappingUtils.class});
classUnderTest = (MappingDef)constructor.newInstance( Mappers.getMapper( MappingUtils.class ));
}
#Test
void test() {
// test all your mappings (null's in source, etc)..
}
As a variant on Sjaak’s answer, it is now possible to rely on MapStruct itself to retrieve the implementation class while also avoiding casts by properly using generics:
Class<? extends MappingDef> mapperClass = Mappers.getMapperClass(MappingDef.class);
Constructor<? extends MappingDef> constructor = mapperClass.getConstructor(MappingUtils.class);
MappingDef mappingDef = constructor.newInstance(Mappers.getMapper(MappingUtils.class));
This could probably even be made completely generic by inspecting the constructor, finding all mappers it requires as arguments and recursively resolving those mappers.
So, try this:
Maven:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
#ComponentScan(basePackageClasses = NoteServiceTest.class)
#Configuration
public class NoteServiceTest {
#Autowired
private SomeClass someClass;
private ConfigurableApplicationContext context;
#Before
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
#After
public void springDown() {
if ( context != null ) {
context.close();
}
}
#test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
Even better would be use constructor injection all the way... Also in SomeClass and by using #Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR).. Then you don't need spring / spring mocking in your test cases.
As mentioned you can use the injectionStrategy = InjectionStrategy.CONSTRUCTOR for mappers using other mappers (in this case for the MappingDef).
And than in the test simply:
#Spy
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
#Spy
MappingDef mappingDef = new MappingDefImpl(mappingUtils);
Maybe not the most elegant but it works.
There is no need to use reflections. The simplest way for me was the following:
#RunWith(MockitoJUnitRunner.class)
public class NoteServiceTest {
#InjectMocks
private SomeClass someClass;
#Spy
#InjectMocks
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
#Spy
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
this however only works on the first level of nested Mappers. If you have a Mapper that uses a Mapper which uses a thrid Mapper than you need to use ReflectionTestUtils to Inject the third Mapper into the second Mapper.
Related
I use Spring Boot 5 and JUnit in my project. I create a unit test to test the service.
Here is the service that I am testing:
#Service
#RequiredArgsConstructor
#Slf4j
public class BuilderServiceImpl implements BuilderService{
#Autowired
public AutoMapper autoMapper;
private final BuilderRepository builderRepository;
private final AdminUserRepository adminUserRepository;
#Override
public BuilderDto getByEmail(String email){
}
#Override
public List<BuilderMinDto> getAll() {}
#Override
public List<BuilderMinDto> getAll(int page, int size) {}
#Override
public SaveBuilderResponse create(Builder builder){
var str = autoMapper.getDummyText();
Builder savedBuilder = builderRepository.save(builder);
return new SaveBuilderResponse(savedBuilder);
}
}
And here is the test class that tests the service above:
#SpringBootTest
#RequiredArgsConstructor
#Slf4j
class BuilderServiceImplTest {
#Mock
private BuilderRepository builderRepository;
#Mock
private AdminUserRepository adminUserRepository;
private AutoCloseable autoCloseable;
private BuilderService underTest;
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
#AfterEach
void tearDown () throws Exception{
autoCloseable.close();
}
#Test
void getByEmail(){}
#Test
#Disabled
void getAll() { }
#Test
#Disabled
void testGetAll() {}
#Test
void create() {
//given
Builder builder = new Builder();
builder.setName("John Johnson");
builder.setCompanyName("Builders Test");
builder.setEmail("test#builders.com");
//when
underTest.create(builder);
//then
ArgumentCaptor<Builder> builderArgumentCaptor = ArgumentCaptor.forClass(Builder.class);
verify(builderRepository)
.save(builderArgumentCaptor.capture());
Builder captureBuilder = builderArgumentCaptor.getValue();
assertThat(captureBuilder).isEqualTo(builder);
}
}
When I start to run the test class the create method in BuilderServiceImpl fired and on this row:
var str = autoMapper.getDummyText();
I get NullPointerException(autoMapper instance is null).
Here is the definition of AutoMapper class:
#Component
#Slf4j
#RequiredArgsConstructor
public class AutoMapper {
public String getDummyText(){
return "Hello From AutoMapper.";
}
}
As you can see I use #Component annotation to register the AutoMapper class to the IoC container and Autowired annotation to inject it into autoMapper property in BuilderServiceImpl class.
Why autoMapper instance is null? How can I make autoMapper to be initialized?
In order to make #Autowire work you have to use the instance of BuilderServiceImpl (object under test) created by spring itself.
When you create the object like this (by yourself, manually):
#BeforeEach
void setUp(){
....
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
}
Spring doesn't know anything about this object, hence Autowiring won't work
Another thing that might be useful:
You've used #Mock for BuilderRepository and AdminUserRepository.
This are plain mockito annotation, and if you're using an integration/system test that runs the spring under the hood, probably this is not what you want:
Surely, it will create a mock, but it won't put it onto an application context, and won't substitute the beans of these classes that might have been created by spring.
So if this is what you want to achieve, you should use #MockBean instead.
This annotation belongs to Spring Testing framework rather than a plain mockito annotation.
All-in-all you might end up with something like this:
#SpringBootTest
class MyTest
{
#MockBean
BuilderRepository builderRepo;
#MockBean
AdminUserRepository adminUserRepo;
#Autowired // spring will inject your mock repository implementations
// automatically
BuilderServiceImpl underTest;
#Test
void mytest() {
...
}
}
Add #Autowire annotation Annotations on below fields. Error due your not initialized below object In BuilderServiceImpl
#Autowire
private final BuilderRepository builderRepository;
#Autowire
private final AdminUserRepository adminUserRepository;
Why are you creating BuildService manually? If you do this, set AutoMapper manualy too.
#BeforeEach
void setUp(){
autoCloseable = MockitoAnnotations.openMocks(this);
underTest = new BuilderServiceImpl(builderRepository,adminUserRepository);
underTest.setAutoMapper(new AutoMapper());
}
You are not using di.
I have a User class which has a constructor with two parameter of same type.
public class User {
Dependency dependency1;
Dependency dependency2;
User(Dependency dependency1,Dependency dependency2){
this.dependency1=dependency1;
this.dependency2=dependency2;
}
public void test(){
dependency1.print();
dependency2.print();
}
}
In my test, I have two Spy Dependency and I want them to be injected like new User(dependency1,dependency2).
#ExtendWith(MockitoExtension.class)
public class InjectMocksTest {
#InjectMocks
User user;
#Spy
Dependency dependency1=new Dependency("dependent1");
#Spy
Dependency dependency2=new Dependency("dependent2");
#Test
void test(){
user.test();
}
}
But I find that dependency1 and dependency2 of User both refers to dependency1 in the test, like they are injected with new User(dependency1,dependency1).
So how to achieve what I want with #InjectMocks annotation?
It appears that you can trick Mockito into injecting the mocks using Field injection if you create the object itself first, and set the arguments to null. This works for me:
#ExtendWith(MockitoExtension.class)
public class UserTest {
#InjectMocks
User user = new User(null, null);
#Spy
Dependency dependency1 = new Dependency("dependent1");
#Spy
Dependency dependency2 = new Dependency("dependent2");
#Test
void test() {
user.test();
}
}
Output:
dependent1
dependent2
However, this behavior is not documented, so I am not sure I would depend upon it.
My code is setup like this.
abstract class BaseController {
#Inject Store store;
}
class MyController extends BaseController {
private final Validator validator;
#Inject
public MyController(Validator validator) {
this.validator = validator;
}
public boolean someMethod() {
a = store.storingMethod();
b = validator.validate(a);
...
...
return true;
}
}
Now I wanted to write tests for myController. In the test, I want to use the injected Store but I want to mock out the Validator.
I tried something like this.
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest() {
private MyController myController;
#Mock private Validator validator;
#Before
public void before() {
myController = new MyController(validator);
}
}
I know, if I move the Store store from the BaseController to MyController, I can initialize it in the constructor (just like I did for the validator). But, I want to have the Store in the Base class as it will be used by other classes extending it.
With the way my classes are set up, How do I inject the Store while testing?
Don't use field injection. Use constructor injection.
abstract class BaseController {
final Store store;
BaseController(Store store) {
this.store = store;
}
}
class MyController extends BaseController {
private final Validator validator;
#Inject
public MyController(Validator validator, Store store) {
super(store);
this.validator = validator;
}
}
There is a bit of debate on the subject, but your example is a clear example of a situation in which the use of field injection makes a class much harder to test.
Spring #Autowire on Properties vs Constructor
Dependency Injection: Field Injection vs Constructor Injection?
It is also worth noting that
The Spring team generally advocates constructor injection
Source
I usually solve this using the following pattern:
abstract class BaseController {
private final Store store;
protected BaseController (Store store) {
this.store = store;
}
protected Store getStore() {
return this.store;
}
}
class MyController extends BaseController {
private final Validator validator;
#Inject
public MyController(Store store, Validator validator) {
super(store);
this.validator = validator;
}
public boolean someMethod() {
a = getStore().storingMethod();
b = validator.validate(a);
...
...
return true;
}
}
So the base class can be used regardless of any injection framework available.
You can use ReflectionTestUtils to set your field value.
Import it in your pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
<scope>test</scope>
</dependency>
Use it to set your store:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest() {
private MyController myController;
#Mock private Validator validator;
#Before
public void before() {
myController = new MyController(validator);
ReflectionTestUtils.setField(myController, "store", new YourTestStore());
// more testing
}
}
More info on that # https://www.baeldung.com/spring-reflection-test-utils
Also, note that I do not think this is best practice.
I would like to know what is the best way to write unit tests in this context :
MyApi :
#RestController
public class MyApi{
#Autowired
MyAction myAction;
#PostMapping
public ResponseEntity addAction(#ResponseBody MyDto myDto){
return myAction.addAction(myDto);
}
}
MyAction :
#Service
public class MyAction{
#Autowired
private MyClient myClient;
public ResponseEntity<AuthenticationResponseDto> login(MyDto myDto{
return ResponseEntity.ok(myClient.addClient(myDto));
}
}
For example, is it mandatory to add constructor ?
Thanks
It's considered a good practice to use constructor injection, however if you don't want to use it you need to use #Mock and #InjectMocks. It uses reflection and constructor is not required to be defined.
#RunWith(MockitoJUnitRunner.class)
public class Test {
#Mock
private Client client;
#InjectMocks
private ServiceImpl plannerService = new ServiceImpl();
#Test
public void test() throws Exception {
....
}
}
I'm sure there is a way to avoid using an autowired constructor and just autowiring a field, however I use constructors as I consider it a good practice. It also makes it easy to inject a mocked object like so
#Mock
MyAction myAction;
MyApi myApi;
ResponseEntity<AuthenticationResponseDto> testResponse = ResponseEntity.ok
(new AuthenticationResponseDto());
#Before
public void setup(){
myApi = new MyApi(myAction);
}
#Test
public void simpleMyApiTestExample (){
when(myAction.login(any())).thenAnswer(i-> testRespone);
ResponseEntity<?> actualResponse = myApi.addAction(new MyDto());
assertThat(actualResponse).isSameAs(testResponse);
}
Just to give you an idea. I just wrote this example in the SO text editor, so appologies for any typos/mistakes. But hopefully this shows why having constructors is useful for testing things that are autowired. It allows you to mock the objects necessary for instantiation by adding them to the constructor. In this example this would probably also apply to MyDto and AuthenticationResponseDto objects as well.
I am new to spring boot. Need some suggestions
Here my unit test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DemoApplication.class)
public class EmployeeRepositoryTest {
#Autowired
protected EmployeeRepository employeeRepository;
#Test
public void insertEmploee(){
Employee employee = new Employee();
employee.setEmpName("Azad");
employee.setEmpDesignation("Engg");
employee.setEmpSalary(12.5f);
employeeRepository.save(employee);
}
}
When I run it I get exception as
java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotationAttributes(Ljava/lang/reflect/AnnotatedElement;Ljava/lang/String;ZZ)Lorg/springframework/core/annotation/AnnotationAttributes;
at org.springframework.test.util.MetaAnnotationUtils$AnnotationDescriptor.<init>(MetaAnnotationUtils.java:290)
at org.springframework.test.util.MetaAnnotationUtils$UntypedAnnotationDescriptor.<init>(MetaAnnotationUtils.java:365)
at org.springframework.test.util.MetaAnnotationUtils$UntypedAnnotationDescriptor.<init>(MetaAnnotationUtils.java:360)
at org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes(MetaAnnotationUtils.java:191)
at org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes(MetaAnnotationUtils.java:198)
at
Process finished with exit code -1
It seems that your problem is solved (mixing the Spring dependency versions) but let me just expand the comment from #g00glen00b on how to write unit tests.
Make sure the following dependency is in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
As pointed out in the comment, #RunWith(SpringJUnit4ClassRunner.class) causes the unit test to start the whole application and it is used rather for integration testing.
Fortunately, Spring-boot has built in dependency for Mockito which is just what you need for unit tests like this.
Now, your unit test could look something like this:
public class EmployeeRepositoryTest {
#InjectMocks
private EmployeeRepository employeeRepository;
#Mock
private Something something; // some class that is used inside EmployRepository (if any) and needs to be injected
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void insertEmploee(){
Employee employee = new Employee();
employee.setEmpName("Azad");
employee.setEmpDesignation("Engg");
employee.setEmpSalary(12.5f);
employeeRepository.save(employee);
Mockito.verify(...); // verify what needs to be verified
}
}
Nice post about using Mockito can be found, for example, here.
Instead of using #Autowired on EmployeeRepository we can use #MockBean cause we are writing unit tests we don't need to deal with the real data we just need to verify that the function is working fine or not. Check the below code
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Main.class)//main springboot class
#WebAppConfiguration
public abstract class AbstractBaseTest {
protected MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
}
public class EmployeeRepositoryTest extends AbstractBaseTest{
#MockBean
protected EmployeeRepository employeeRepository;
#Override
#Before
public void setUp() {
super.setUp();
}
#Test
public void insertEmploee(){
Employee employee = new Employee();
employee.setEmpName("Azad");
employee.setEmpDesignation("Engg");
employee.setEmpSalary(12.5f);
Mockito.doNothing().when(employeeRepository).save(Mockito.any(Employee.class));
employeeRepository.save(employee);
Mockito.verify(employeeRepository, Mockito.times(1)).save(employee);
}
}