Testing Spring service class which uses an Autowired object - java

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());
// ...
}
}

Related

Java - Spring and Mockito - Mocking a constructor parameter that is not a field in class?

I have the following Spring component that I am trying to unit test:
#Component
public class OrderService {
private final DatabaseConnection dbConnection;
private final String collectionName;
#Autowired
public OrderService(
DatabaseConnection dbConnection,
DatabaseConfig databaseConfig) {
this.dbConnection = dbConnection;
this.collectionName = databaseConfig.getCollectionName();
}
//methods etc...
}
The DatabaseConfig class is as follows:
#ConfigurationProperties(prefix = "database")
#ConstructorBinding
public class DatabaseConfig {
//methods etc...
}
I am trying to inject mocks in my OrderService class as follows:
#RunWith(MockitoJUnitRunner.class)
class OrderServiceTest {
#InjectMocks
OrderService orderService;
#Mock
DatabaseConnection dbConnection; // working as expected
#Mock
DatabaseConfig databaseConfig; // giving null pointer
#Mock
DatabaseCollectionConfig databaseCollectionConfig;
#BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
when(databaseConfig.getCollections()).thenReturn(databaseCollectionConfig);
when(databaseCollectionConfig.getCollectionName()).thenReturn("myCollection");
}
When I run my test class I get:
org.mockito.exceptions.misusing.InjectMocksException:
Cannot instantiate #InjectMocks field named 'OrderService' of type 'class com.my.package.OrderService'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : null
The issue is that in the OrderService constructor when I debug this line is coming as null:
this.collectionName = databaseConfig.getCollectionName();
How can I correctly mock databaseConfig.getCollectionName() to solve the null issue?
No need to use #InjectMock annotation because you are using constructor based injection in your service class. Please rewrite your test case like this and try again.
#RunWith(MockitoJUnitRunner.class)
class OrderServiceTest {
OrderService orderService;
#Mock
DatabaseConnection dbConnection; // working as expected
#Mock
DatabaseConfig databaseConfig; // giving null pointer
#Mock
DatabaseCollectionConfig databaseCollectionConfig;
#BeforeEach
public void setup(){
when(databaseConfig.getCollections()).thenReturn(databaseCollectionConfig);
when(databaseCollectionConfig.getCollectionName()).thenReturn("myCollection");
orderService = new OrderService(dbConnection, databaseConfig);
}
}
You can try to create a mock for that method and create an object instance instead of using the InjectMocks annotation.
#RunWith(MockitoJUnitRunner.class)
class OrderServiceTest {
OrderService orderService;
#Mock
DatabaseConnection dbConnection; // working as expected
#Mock
DatabaseConfig databaseConfig; // giving null pointer
#Mock
DatabaseCollectionConfig databaseCollectionConfig;
#BeforeEach
public void setup() {
(...)
when(databaseConfig.getCollections()).thenReturn(databaseCollectionConfig);
when(databaseCollectionConfig.getCollectionName()).thenReturn("myCollection");
orderService = new OrderService(dbConnection, databaseConfig);
}
Mock behavior, not values. An #ConfigurationProperties class is just a container for data; it doesn't (generally speaking) do anything. Create a real one with new using your test values (e.g., setCollections(testDatabaseCollectionConfig)).

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.

JUnit with Spring Boot in n-tier Service Layer

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());
}

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.

autowired dependency not getting mocked while executing test case with junit and mockito

I am using Junit4 and Mockito for test cases, in the following code dbprop.getProperty("config") is throwing a NullPointerException because dbProp is null. Please help me out why it was not mocked?
public abstract class BaseClass {
#Autowired
protected DBproperties dbprop;
}
public class SampleClass extends BaseClass {
#Autowired
private OrderService orderService;
valdiateOrder(String input) {
String config = dbprop.getProperty("config");
}
}
public class TestSampleClass {
#InjectMocks
SampleClass sampleClass;
#Mock
private OrderService orderService;
#Test
public void testValidateOrder() {
DBproperties dbprop = mock(DBproperties .class);
when(dbprop.getProperty("config")).thenReturn("xxxx");
assertNotNull(SampleClass.valdiateOrder("xxx"));
}
}
Your dbprop mock has not been injected into sampleClass, you need to add:
#Mock
private DBproperties dbprop;
Then remove the dbprop mock creation from your test method:
#Test
public void testValidateOrder() {
// DBproperties dbprop = mock(DBproperties .class); <-- removed
when(dbprop.getProperty("config")).thenReturn("xxxx");
assertNotNull(SampleClass.valdiateOrder("xxx"));
}
Next, to ensure mocks are injected when using the #InjectMocks annotations you need to either add the following runner:
#RunWith(MockitoJUnitRunner.class)
public class TestSampleClass {
...
Or call the following in a #Before method:
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
See the MockitoAnnotations and MockitoJUnitRunner JavaDocs for more information on the two approaches.
You can annotate your Object with #Mock, so its look like this
#Mock
DBproperties dbProperties;#Before public void init(){ MockitoAnnotations.initMocks(this);
}

Categories