How to test Service class methods which uses Mapstruct mappers in Springboot? - java

#Mapper(uses = SomeMapper.class,imports = Date.class)
public interface DomainModelMapper {
Model domainToModel(Domain domain);
#Mapping(target="dateUpdated", source="dateUpdated" ,defaultExpression = "java(Date.from(java.time.OffsetDateTime.now().toInstant()))")
#Mapping(target="id.key",source="id.key",defaultExpression = "java(com.datastax.driver.core.utils.UUIDs.timeBased())")
Domain modelToDomain(Model model);
}
I have a mapper class to do some Date conversions
public class SomeMapper {
public Date OffsetDateTimeToDate(OffsetDateTime offsetDateTime) {
return offsetDateTime != null ? Date.from(offsetDateTime.toInstant()):null;
}
public OffsetDateTime DateToOffsetDateTime(Date date) {
return date != null ? date.toInstant().atOffset(ZoneOffset.UTC) : null;
}
}
This is my service class where I use DomainModelMapper
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
someRepository someRepository;
private final DomainModelMapper domainToModelMapper =
Mappers.getMapper(DomainModelMapper.class);
#Override
public Model saveSomething(Model model) {
return DomainModelMapper.domainToModel(someRepository
.save(DomainModelMapper.modelToDomain(model)));
}
How can I unit test saveSomething(Model model) method? How I can inject Mapstruct classes or mock them?

If you make the #Mapper interface as a Spring-based component model, then it can be autowired through #Autowired annotation. Read more at 4.2. Using dependency injection
#Mapper(uses = SomeMapper.class,imports = Date.class, componentModel = "spring")
public interface DomainModelMapper {
// IMPLEMENTATION
}
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
SomeRepository someRepository;
#Autowired
DomainModelMapper domainModelMapper;
// THE REST OF THE IMPLEMENTATION
}
The testing becomes fairly easy since all the beans can be also injected in the #SpringBootTest with the #Autowired annotation.
The DomainModelMapper can be autowired and used in the unit test as is and rely on its implementation
The SomeRepository shall be either mocked using #MockBean which overwrites an existing bean or creates a new one if none of that type exists... or autowired as in the implementation if you use an in-memory database for the testing phase, such as H2.
In any case, the test class will be ready for testing.
#SpringBootTest
public class SomeServiceTest {
#Autowired // or #MockBean
SomeRepository someRepository;
#Autowired // no need to mock it
DomainModelMapper domainModelMapper;
#Test
public void test() {
// TEST
}
}

Related

Should I instantiate my Service in order to share infornation between different methods?

Currently I have an endpoint in my controller that calls different methods from my service. Each of these methods recieve a parameter called executionId that is different for every http request.
#RestController
public class ProcedureController {
#Autowired
MyService service;
#GetMapping
public void execute(){
String executionId = ...; //randomly generated
service.executeMethodA(executionId);
service.executeMethodB(executionId);
}
}
#Service
public class MyService {
public void executeMethodA(String executionId){
...
}
public void executeMethodB(String executionId){
...
}
}
It works fine but it seems very repetitive to pass executionId as a parameter to each method. So one solution that I thought was to instantiate my service with the executionId so it can hold this information and every method can access it.
#RestController
public class ProcedureController {
#GetMapping
public void execute(){
String executionId = ...; //randomly generated
MyService service = new MyService(executionId);
service.executeMethodA();
service.executeMethodB();
}
}
#Service
public class MyService {
String executionId;
public MyService(String executionId){
this.executionId = executionId;
}
public void executeMethodA(){
// Use executionId here
...
}
public void executeMethodB(){
// Use executionId here
...
}
}
Is this solution a bad practice? If so, how can I achieve something similar?
Spring allows you to inject a managed object (bean) as a dependency into another object via the #Autowired annotation.
For example, if I have a UserService that has a dependency on UserRepository, I can have the UserRepository injected using #Autowired annotation like this:
UserRepository Class
class UserRepository {
UserRepository () {}
}
UserService Class
class UserService {
#Autowired
private UserRepository userRepository;
UserService () {}
}
This is done using Field Injection. The same thing can be accomplice using Setter Injection:
class UserService {
private UserRepository userRepository;
UserService () {}
#Autowired // Using setter injection
public void setUserRepository(
UserRepository userRepository) {
this.userRepository = userRepository
}
}
or via Constructor Injection:
class UserService {
private UserRepository userRepository;
#Autowired // Using constructor Injection
UserService (UserRepository userRepository) {
this.userRepository = userRepository
}
}

How to use Dependency Injection with Spring Boot Unit Testing?

Is it possible to use dependency injection with unit tests using Spring Boot? For integration testing #SpringBootTest start the whole application context and container services. But is it possible to enable dependency injection functionality at unit test granularity?
Here's the example code
#ExtendWith(SpringExtension.class)
public class MyServiceTest {
#MockBean
private MyRepository repo;
#Autowired
private MyService service; // <-- this is null
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Or should I just manage the SUT (service class) as POJO and manually inject the mocked dependencies? I want to keep tests fast but minimize boilerplate code.
As #M.Deinum mentioned in the comments, unit tests shouldn't use dependency injection. Mock MyRepository and inject MyService using Mockito (and Junit5):
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#InjectMocks
private MyService service;
#Mock
private MyRepository repo;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
If you want to test the repository, use #DataJpaTest. From the docs:
Using this annotation will disable full auto-configuration and instead
apply only configuration relevant to JPA tests.
#DataJpaTest
public class MyRepositorTest {
#Autowired
// This is injected by #DataJpaTest as in-memory database
private MyRepository repo;
#Test
void testCount() {
repo.save(new MyEntity("hello"));
repo.save(new MyEntity("world"));
assertEquals(2, repo.count());
}
}
In conclusion, the suggested approach is to test the service layer mocking the repository layer with Mockito (or similar library) and to test the repository layer with #DataJpaTest.
You have not added the #Autowired in service for MyRepository
Service Class
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
#Autowired
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Service Test Class
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MyRepository repo;
#InjectMocks
private MyService service;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}

#Repository instance is null when invoking a method using a #Service instance from a unit test

My goal is to use an in-memory database for these unit tests, and those dependancies are listed as:
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
So that the repository instance actually interacts with a DB, and I dont just mock return values.
The problem is that when I run my unit test, the repository instance inside the service instance is null.
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
This is the console output when running my unit test:
null
java.lang.NullPointerException
at com.my.MyService.findAll(MyService.java:20)
at com.my.MyTest.testMy(MyTest.java:23)
My unit test class:
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
My service class:
#Service
public class MyService {
#Autowired
MyRepository myRepository;
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
My repository class:
#Repository
public interface MyRepository extends CrudRepository<MyEntity, Long> {
}
My entity class:
#Entity
public class MyEntity {
#Id
#GeneratedValue
public Long id;
}
Why is that? Am I missing some annotation on the unit test class to initialise the repository instance?
Basically yes :)
You need to initialise a Spring Context by Annotating your Testclass with #SpringBootTest
The other Problem you have is that you create your MyService Object manually.
By doing so SpringBoot has no chance to inject any Bean for you. You can fix this by simply injecting your MyService in your Testclass. Your Code should look something like this:
#SpringBootTest
public class MyTest {
#Autowired
private MyService myService;
#Test
void testMy() {
int size = myService.findAll().size();
assertEquals(0, size);
}
}
To use #MockBean annotation, you have to use SpringRunner to run the test. Use #RunWith Annotation on top of your test class and pass SpringRunner.class.
#RunWith(SpringRunner.class)
public class MyTest {
#MockBean
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService();
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
The problem here is your service implementation. Using #Autowired to inject the dependency will work when you run the whole app, but it do not allow you to inject a custom dependency when you'll need it, and a good example of this is testing.
Change your service implementation to:
#Service
public class MyService {
private MyRepository myRepository;
public MyService(MyRepository myRepository){
this.myRepository = myRepository;
}
public List<MyEntity> findAll() {
System.out.println(myRepository); // null
return (List<MyEntity>) myRepository.findAll(); // throws NullPointerException
}
#Transactional
public MyEntity create(MyEntity myEntity) {
myRepository.save(myEntity);
return myEntity;
}
}
This constructor will be called by spring. Then change your test to:
public class MyTest {
#Mock
MyRepository myRepository;
#Test
void testMy() {
MyService myService = new MyService(myRepository);
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
Note I have replaced #MockBean to #Mock as the previous annotation is for injecting a mock bean into the spring context, which is not needed if you're doing unit testing. If you want to boot spring context (which I would not recommend you) you need to configure your test class with #SpringBootTest or some of the other available alternatives. That will convert your test into an integration test.
PD: This test will not work if you don't provide a mock to myRepository.findAll(). Mockito default behaviour is to return null, but you're expecting it to return 0, so you'll need to do something like given(myRepository.findAll()).willReturn(0).
I believe you wish to write an integration test. Here you could remove the MockBean annotation and simply autowire your repository. Also, run with The SpringRunner class.
#RunWith(SpringRunner.class)
public class MyTest {
#Autowired
MyRepository myRepository;
#Autowired
MyService myService
#Test
void testMy() {
int size = myService.findAll().size();
Assertions.assertEquals(0, size);
}
}
This should work

Spring inject mocked Bean

I created a validation constraint depending on a repository.
public class PersonValidator implements ConstraintValidator {
#Autowired
private PersonRepository personRepository;
#Override
public void initialize(PersonValidator personValidator) {
}
#Override
public boolean isValid(Person person, ConstraintValidatorContext context) {
return null != repository.findByName(person.getName());
}
}
Testing the validator itself is easy by mocking the PersonValidator but I want to test the integration with the validator to check the validation message for example.
public class PersonValidatorTest {
#Autowired
private Validator validator;
#Test
public void integration() {
Person person = new Person();
person.setName("person");
Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
Assert.assertEquals(0, constraintViolations.size());
}
}
I don't know how to inject a PersonValidator inside the Validator with the mocked repository.
Try running test with SpringJUnit4ClassRunner, and create a mock repository bean and annotate with spring's #Primary annotation or mark as primary in bean definition for test to autowire mock repository.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"/context.xml", "/test-context.xml"})
public class PersonValidatorTest {
#Autowired
private Validator validator;
....
You can create mock repository using mockito factory bean as below
public class MockitoFactoryBean<T> implements FactoryBean<T> {
private Class<T> classToBeMocked;
public MockitoFactoryBean(Class<T> classToBeMocked) {
this.classToBeMocked = classToBeMocked;
}
#Override
public T getObject() throws Exception {
return Mockito.mock(classToBeMocked);
}
....
and then create spring's context file 'test-context.xml' for test repository
<bean id="mockRepository" primary="true" class="com.test.mock.MockitoFactoryBean">
<constructor-arg value="com....PersonRepository"/>
</bean>
try this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "context.xml")
public class PersonValidatorTest {
#Autowired
private Validator validator;
...
I prefer setter injection, which makes injecting mocks simple during tests.
Or you can also use reflection based injection with a spring util class.

How to inject mock into #Service that has #Transactional

I have any issue in my unit test where I have something along the lines of this. The mock injection get overridden on the someService if the blargh function is annotated with Transactional. If I remove the Transactional the mock stays there. From watching the code it appears that Spring lazily loads the services when a function in the service is annotated with transactinal, but eagerly loads the services when it isn't. This overrides the mock I injected.
Is there a better way to do this?
#Component
public class SomeTests
{
#Autowired
private SomeService someService;
#Test
#Transactional
public void test(){
FooBar fooBarMock = mock(FooBar.class);
ReflectionTestUtils.setField(someService, "fooBar", fooBarMock);
}
}
#Service
public class someService
{
#Autowired FooBar foobar;
#Transactional // <-- this causes the mocked item to be overridden
public void blargh()
{
fooBar.doStuff();
}
}
Probably you could try to implement your test in the following way:
#Component
#RunWith(MockitoJUnitRunner.class)
public class SomeTests
{
#Mock private FooBar foobar;
#InjectMocks private final SomeService someService = new SomeService();
#Test
#Transactional
public void test(){
when(fooBar.doStuff()).then....;
someService.blargh() .....
}
}
I could not try it right now as don't have your config and related code. But this is one of the common way to test the service logic.
Use the Spring #Profile functionality - beans can be associated to a certain group, and the group can be activated or deactivated via annotations.
Check this blog post and the documentation for more detailed instructions, this is an example of how to define production services and two groups of mock services:
#Configuration
#Profile("production")
public static class ProductionConfig {
#Bean
public InvoiceService realInvoiceService() {
...
}
...
}
#Configuration
#Profile("testServices")
public static class TestConfiguration {
#Bean
public InvoiceService mockedInvoiceService() {
...
}
...
}
#Configuration
#Profile("otherTestServices")
public static class OtherTestConfiguration {
#Bean
public InvoiceService otherMockedInvoiceService() {
...
}
...
}
And this is how to use them in the tests:
#ActiveProfiles("testServices")
public class MyTest extends SpringContextTestCase {
#Autowired
private MyService mockedService;
// ...
}
#ActiveProfiles("otherTestServices")
public class MyOtherTest extends SpringContextTestCase {
#Autowired
private MyService myOtherMockedService;
// ...
}
I have the exact same problem and I solve it by using Mockito.any() for the arguments
eg:
when(transactionalService.validateProduct(id)).thenReturn("")
=> when(transactionalService.validateProduct(Mockito.any())).thenReturn("")

Categories

Resources