JUnit with Spring Boot in n-tier Service Layer - java

I have a model
public class Student {
private String name;
// getter and setter
}
I have implemented a REST controller which generates a student object with random chars as name in it. I have a #RestController, bind with a #Service layer which serves student object and again a #Service layer which generates random strings. I want to test my application using JUnit by mocking my controller and services. The problem is I can mock my controller and service that serves student but stringGeneratorService service layer is not mocked.
My Controller
#RestController
public class StudentServer {
StudentService service;
#Autowired
public StudentServer(StudentService service) {
this.service = service;
}
#GetMapping("/generate-student")
public Student studentGenerator() {
return service.getRandomStudent();
}
}
My Service layer that serves student object
#Service("studentService")
public class StudentService {
StringGeneratorService stringService;
#Autowired
public StudentService(StringGeneratorService stringService) {
this.stringService = stringService;
}
public Student getRandomStudent() {
Student student = new Student();
student.setName(stringService.generateRandomAlphaString());
return student;
}
}
And my RandomStringGenertor Service
#Service("stringGeneratorService")
public class StringGeneratorService {
Random random = new Random();
public String generateRandomAlphaNumericString() {
// returns a randomly generated string
}
}
My JUnit test class as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(StudentServer.class)
public class RestTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
public static class TestConfig {
#Bean
public StudentService studentService(final StringGeneratorService stringGeneratorService){
return new StudentService(stringGeneratorService);
}
#Bean
public StringGeneratorService stringGeneratorService(){
return mock(StringGeneratorService.class);
}
}
#Autowired
private StudentService studentService;
#Autowired
public StringGeneratorService stringGeneratorService;
#Before
public void setUp() {
reset(stringGeneratorService);
}
#After
public void tearDown() {
verifyNoMoreInteractions(stringGeneratorService);
}
#Test
public void testGenerateStudent() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/generate-student"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(print())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").isNotEmpty());
}
}
The result is Body = {"name":null}
Can anybody have any idea what am I doing wrong?

In your test configuration to define a mock for your StringGeneratorService: return mock(StringGeneratorService.class);, but you don't define a mocked behavior of a method. The default behavior of many method returning an Object is to return null, which you see in your result.
You need to define a mocked behavior of the method like this:
#Bean
public StringGeneratorService stringGeneratorService() {
StringGeneratorService mock = mock(StringGeneratorService.class);
// adding the missing mocked method behavior here
when(mock.generateRandomAlphaNumericString()).thenReturn("expectedMockedValue");
return mock;
}

As I see this is a unit test for your Rest Controller class.
I would suggest naming the Test Class as below as a good practice
Class Name:StudentServer.class
Junit Class Name: StudentServerTest.class
Now Coming to your real issue.
1] I see you have autowired your dependency class (StudentService.class). Instead you need to mock it as
#MockBean
private StudentService studentService;
2] You do not need StringGeneratorService bean/object in your test class because, its method is not directly called from your controller class.
Its method is instead called from your service class and since, you mock your service class itself, this class is not visible in your controller class at all.
Remove this
#Autowired
public StringGeneratorService stringGeneratorService;
3] Now define the mock behavior as below and then assert
#Test
public void testGenerateStudent() throws Exception {
Student student = new Student();
student.setName("TestStudent");
Mockito.when(studentService.getRandomStudent()).thenReturn(student);
mockMvc.perform(MockMvcRequestBuilders.get("/generate-student"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(print())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").isNotEmpty());
}

Related

Testing Spring service class which uses an Autowired object

I have to test a Spring service which uses an Autowired object, see the following code:
#Service
public class MyService {
#Autowired
ExternalService externalService;
public String methodToTest(String myArg) {
String response = externalService.call(myArg);
// ...
return ...;
}
What I tried to do in my test class, using Mockito, is to mock the externalService's call method as follows:
#ExtendWith(MockitoExtension.class)
public class MySeviceTest {
#Mock
private ExternalService externalService = Mockito.mock(ExternalService.class);
private MySevice mySevice = Mockito.spy(new MySevice());
#Test
public void methodToTest_Test() {
Mockito.when(externalService.call(anyString())).thenReturn(anyString());
// ...
}
}
The problem is at runtime in the class MyService because the externalService object is null, and as a result I get the null pointer exception. So, what's the right method to write this type of test?
You get a null pointer exception because you did not set the property 'externalService'. #Autowired only works when running with Spring. For your test you have to inject your mock yourself:
#ExtendWith(MockitoExtension.class)
public class MySeviceTest {
#Mock
private ExternalService externalService = Mockito.mock(ExternalService.class);
private MySevice mySevice = Mockito.spy(new MySevice());
#Test
public void methodToTest_Test() {
myService.externalService = externalService //inject your mock via the property
Mockito.when(externalService.call(anyString())).thenReturn(anyString());
// ...
}
}

How to write spring boot unit test for a method which calls another #Async method inside it?

I need to write integration test for "processEvent" method which calls a #Async method inside it. I tried writing a test for this method, but the issue was it did not save the Student object to the DB. However removing #Async annotation allows me to save the object. I want to know how should I write Test cases for this method, eliminating the #Async issue. I want to save the student object while testing. I have attached my code and below.
Here is the ClassA and it has the method I want to test.
#Service
public class ClassA {
private final ConfigurationProcessor<Student> configurationProcessor;
#Autowired
public ClassA(ConfigurationProcessor<Student> configurationProcessor) {
this.configurationProcessor = configurationProcessor;
}
public void processEvent(Configuration configuration) {
configurationProcessor.process(configuration);
}
}
This is the interface ConfigurationProcessor class
public interface ConfigurationProcessor<T> {
void process(Configuration configuration);
}
And this is its Impl class
#Service
public class ConfigurationProcessoeStudents10Impl implements ConfigurationProcessor<Student> {
private final StudentRepository studentRepository;
#Autowired
public ConfigurationProcessoeStudents10Impl(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
#Override
#Async
public void process(Configuration configuration) {
studentRepository.save(Student.builder().Name(configuration.name).Age(configuration.age));
}
}
This is the Test I have written so far.
#EnableAutoConfiguration
#SpringBootTest
public class AudienceC10IT {
#Autowired
ClassA classA;
#Test
#Tag("VerifyProcess")
#DisplayName("Verify kafka event consumer from configuration manager")
void verifyProcess(){
Configuration configuration = new Configuration("lal",12);
classA.processEvent(configuration);
}
}
If you have have set up a ThreadPoolTaskExecutor bean you can Autowire it in your test.
Then, after you call your async method, you can await the termination.
Then you can check for the expected behaviour / result.
Something like this:
#Autowired
private ThreadPoolTaskExecutor asyncTaskExecutor;
#Test
void test() {
callAsyncMethod();
boolean terminated = asyncTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
assertAsyncBehaviour();
}

Why tested class based autowire annotation throw null exception?

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.

spring boot integration testing mock method returns null

I wrote simple spring boot application with Controller, Service and Business classes, but while writing integration test the mock method of Service is returning null
MockMainController
#RestController
public class MockMainController {
#Autowired
private MockBusiness mockBusiness;
#GetMapping("request")
public MockOutput mockRequest() {
return mockBusiness.businessLogic(new MockInput());
}
}
MockBusiness
#Service
public class MockBusiness {
#Autowired
private MockService mockService;
public MockOutput businessLogic(MockInput input) {
return mockService.serviceLogic(input);
}
}
MockService
#Service
public class MockService {
#Autowired
private MockUtil mockUtil;
public MockOutput serviceLogic(MockInput input) {
mockUtil.exchange(UriComponentsBuilder.fromUriString(" "), HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<MockOutput>() {
});
return new MockOutput();
}
}
I'm trying to mock the MockService bean in application context using #MockBean
MockControllerTest
#SpringBootTest
#ActiveProfiles("test")
#Profile("test")
#RunWith(SpringJUnit4ClassRunner.class)
public class MockControllerTest {
#Autowired
private MockMainController mockMainController;
#MockBean
private MockService mockService;
#Test
public void controllerTest() {
MockOutput output = mockMainController.mockRequest();
given(this.mockService.serviceLogic(ArgumentMatchers.any(MockInput.class)))
.willReturn(new MockOutput("hello", "success"));
System.out.println(output); //null
}
}
In the test method I created mock service bean using #MockBean I'm not having any error here but System.out.println(output); prints null
You are getting null because of wrong statements order in your test method. You first call controller method and you get what's inside default #MockBean which is in this case null. Swap statement:
MockOutput output = mockMainController.mockRequest();
with
given(this.mockService.serviceLogic(ArgumentMatchers.any(MockInput.class)))
.willReturn(new MockOutput("hello", "success"));
and you will get expected result.

How to prevent Spring from injecting #Autowired references inside a mock?

I want to test a class using Spring + JUnit + Mockito but I don't manage to make it work properly.
Let's say my class references a Service:
#Controller
public class MyController
{
#Autowired
private MyService service;
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
And this Service references a Repository:
#Service
public class MyService {
#Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
When testing the MyController class, I want the service to be mocked. The problem is: even when the service is mocked, Spring tries to inject the repository in the mock.
Here is what I did. Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
#Autowired
private MyController myController;
#Test
public void testDoSomething() {
myController.doSomething();
}
}
Configuration class:
#Configuration
public class MyControllerTestConfiguration {
#Bean
public MyController myController() {
return new MyController();
}
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
And the error I get: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
I tried to initialize the mock using Mockito's #InjectMocks annotation but this fails because the #PostConstruct method is called before the mocks injection, generating a NullPointerException.
And I cannot simply mock the repository because in real life that would make me mock A LOT of classes...
Can anyone help me on this?
Use constructor instead of field injection. That makes testing a lot easier.
#Service
public class MyService {
private final MyRepository repository;
#Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
#Controller
public class MyController {
private final MyService service;
#Autowired
public MyController(MyService service) {
this.service = service;
}
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
This has several advantages:
You don't need Spring in your tests. This allows you to do proper unit tests. It also makes the test incredibly fast (from seconds to milliseconds).
You cannot accidentally create an instance of a class without its dependencies which would result in a NullPointerException.
As #NamshubWriter pointed out:
[The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
Discard the #Configuration class and write a test like this:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
#Mock
private MyRepository repository;
#InjectMocks
private MyService service;
#Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
Use interfaces, especially if you use some kind of AOP (transactions, security, etc), i.e. you'll have interface MyService and class MyServiceImpl.
In configuration you'll have:
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
you should put the #InjectMocks annotation in your controller and #Mock in your service, look:
#Autowired
#InjectMocks
private MyController myController;
#Autowired
#Mock
private MyService myService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testDoSomething() {
myController.doSomething();
}

Categories