I am creating tests for my Springboot application. The application through a Get call, passing my "CarRequest" in the RequestBody
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
it gives me back the car specifications related to that data
{
"name":"",
"plate":"",
"price":"",
"brand":"",
"kilometers":"",
"revisiondate":"",
"owner":""
}
I did this simple test using Mockito but I don't understand why my service is set by default to null, this throws everything in NullPointErexception
public class CarTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private CarService service;
#Autowired
ObjectMapper objectMapper;
#Test
public void TestOk() throws Exception{
CarsRequest carsRequest = new CarsRequest();
Car car = new Car();
List<Car> cars = new ArrayList<>();
//septum the fields of cars and add them to the list
cars.add(car);
Mockito.when(
service.getByPlate("bmw",
"TG23IO", "1500")).thenReturn(cars);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/garage/cars").accept(
MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
System.out.println(result.getResponse());
String expected = "{name:"bmw","plate":"TG23IO","price":"1500","brand":"POL","kilometers":"80000","revisiondate":"2016-03-15","owner":"JohnLocke"}";
JSONAssert.assertEquals(expected, result.getResponse()
.getContentAsString(), false);
}
}
Below I also add my CarService
#Service
public class CarService {
#Autowired
CarRepository carRepository;
#Autowired
ObjectMapper objectMapper;
public List<Cars> getByContratto(String name, String plate, String price) throws JsonProcessingException {
//some code, extraction logic
return cars;
}
}
The application works perfectly, only the test does not work. Being a novice in test writing I can't figure out what the null on my Carservice is due to.
If needed, I can include the Controller Get and the repository as well, but I don't think they can help
I believe you are trying to test the controller. Hence include the following annotations on top of the test class.
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
Also, I can see that in the test code CarService is being referred while the service code that is shared contains DocumentService.
Try adding #RunWith(SpringRunner.class) to your test class.
I suspect that you are using Junit 4 for your tests. In Junit 4 you need to add
#RunWith(SpringRunner.class) in your test otherwise all annotations will be ignored.
For more info check the docs here . 46.3 Testing Spring Boot Applications is the section that answers your question.
I would however recommend you to migrate to Junit 5 if possible.
#RunWith(SpringRunner.class)
public class CarTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private CarService service;
#Autowired
ObjectMapper objectMapper;
#Test
public void TestOk() throws Exception{
// code here
JSONAssert.assertEquals(expected, result.getResponse()
.getContentAsString(), false);
}
}
Assuming, you want to test just only your controller layer (Because you are using mockMvc) and you are using Junit 4 (looks like from your code), you should write your test case as follows
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
public class CarTest {
#MockBean
private SomeService service;
#Autowired
private MockMvc mockMvc;
#Test
public void shouldPassTest() {
BDDAssertions.assertThat(service).isNotNull();
}
}
Note: In example test method is irrelevant, I added just to to illustrate.
Let us know if it helps.
Related
I have this Controller class that I want to test:
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
#Autowired
public AuthController(UserService service, PasswordEncoder encoder) {
this.service = service;
this.encoder = encoder;
}
#Override
public ResponseEntity<SignedInUser> register(#Valid NewUserDto newUser) {
Optional<SignedInUser> createdUser = service.createUser(newUser);
LoggerFactory.getLogger(AuthController.class).info(String.valueOf(createdUser.isPresent()));
if (createdUser.isPresent()) {
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser.get());
}
throw new InsufficientAuthentication("Insufficient info");
}
This is my unit test:
#ExtendWith(MockitoExtension.class)
#JsonTest
public class AuthControllerTest {
#InjectMocks
private AuthController controller;
private MockMvc mockMvc;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
private static SignedInUser testSignedInUser;
private JacksonTester<SignedInUser> signedInTester;
private JacksonTester<NewUserDto> dtoTester;
#BeforeEach
public void setup() {
ObjectMapper mapper = new AppConfig().objectMapper();
JacksonTester.initFields(this, mapper);
MappingJackson2HttpMessageConverter mappingConverter = new MappingJackson2HttpMessageConverter();
mappingConverter.setObjectMapper(mapper);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setControllerAdvice(new RestApiErrorHandler())
.setMessageConverters(mappingConverter)
.build();
initializeTestVariables();
}
private void initializeTestVariables() {
testSignedInUser = new SignedInUser();
testSignedInUser.setId(1L);
testSignedInUser.setRefreshToken("RefreshToken");
testSignedInUser.setAccessToken("AccessToken");
}
#Test
public void testRegister() throws Exception {
NewUserDto dto = new NewUserDto();
dto.setEmail("ttn.nguyen42#gmail.com");
dto.setPassword("ThisIsAPassword");
dto.setName("ThisIsAName");
// Given
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser));
// When
MockHttpServletResponse res = mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoTester.write(dto).getJson()).characterEncoding("utf-8").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse();
// Then
assertThat(res.getStatus()).isEqualTo(HttpStatus.CREATED.value());
assertThat(res.getContentAsString()).isEqualTo(signedInTester.write(testSignedInUser).getJson());
}
}
Problem:
The test failed as I got the "Insufficient info" message, because isPresent() is never true, even with given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser)) already there.
When I try to log service.createUser(dto) inside the test method, its isPresent() is always true.
When I try to log inside the controller method, it is always false.
What I have tried:
I suspect that it is because somehow my mockMvc is wrongly configured so I tried to add #AutoConfigureMockMvc but it ended up telling me that "There is no bean configured for 'mockMvc'". I tried to change the UserService mock to the implementation class of but no use.
Please help, I'm really new into Spring's unit tests. Thank you!
The problem is as you have found the Mockito mocking:
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser))
Specifically, you instruct the mocked service to return an Optional.of(testSignedInUser) if it receives a parameter that is equal to dto. However, depending on the implementation of the equals() method of NewUserDto, this may never occur. For example it returns true only if the same instance is referred to instead of comparing the values of the member variables. Consequently, when passing a dto through the mockMvc it is first serialized and then serialized again by the object mapper, so even though its member variables have the same values, the objects are not considered equal unless you also override the equals() method.
As an alternative, you can relax the mocking to return the Optional.of(testSignedInUser) if any() argument is passed:
given(service.createUser(any())).willReturn(Optional.of(testSignedInUser))
or if the argument isA() specific class:
given(service.createUser(isA(NewUserDto.class))).willReturn(Optional.of(testSignedInUser))
but generally, it is preferred from a testing perspective to be explicit to avoid false positives so for this reason I advise to double check and and / or override the NewUserDto#equals() method if possible.
Thanks to #matsev answer, I was able to solve half of the problem, but there was this thing as well:
#Controller
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
// ...
}
I left the service fields as final, which did not allow Mockito to inject or mutate the dependency. After removing final, I got the test to work now.
EDIT: Please refer to this thread for workaround Mockito, #InjectMocks strange behaviour with final fields
EDIT 2: By design, Mockito does not inject mocks to final fields: https://github.com/mockito/mockito/issues/352
Doing so violates other APIs and can cause issues. One way to fix this is to just use constructor injection, remove #InjectMocks, then you can just use final fields.
// No InjectMocks
private AuthController controller;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
#BeforeEach
public void setup() {
controller = new AuthController(service, encoder);
// ...
}
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.
below is my controller:
#RestController
#RequestMapping("/Employee")
public class Employeecontroller {
#Autowired
Employeeservice empservice;
#PostMapping("/addEmployee") // adding emp details into the table
public Employee addemployee(#RequestBody Employee emp)
{
empservice.saveemp(emp);
return emp;
}
}
this is the empservice class:
#Service
public class Employeeservice {
#Autowired
EmployeeRepository emprepo;
public Employee saveemp(Employee emp) {
System.out.println("inside save emp");
return emprepo.save(emp);
}
}
(here i dont want to call to emprepo.save(emp) method, which is a database call, so i used Mockito.when and then methods in below test class)
below is the test class:
#SpringBootTest
#RunWith(MockitoJUnitRunner.class)
#AutoConfigureMockMvc
class RestServiceApplicationTests {
#Autowired
private MockMvc mvc;
#Autowired
ObjectMapper objectMapper;
#MockBean
Employeeservice empservice;
Employee reqemp = new Employee("Bob", "java");
#Test
public void testaddemp() throws Exception {
when(empservice.saveemp(reqemp)).thenReturn(new Employee(1, "Bob", "java"));
RequestBuilder request = MockMvcRequestBuilders.post("/Employee/addEmployee")
.contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(reqemp));
MockHttpServletResponse returnedemp = (MockHttpServletResponse) mvc.perform(request).andExpect(status().isOk())
.andReturn().getResponse();
Employee expectedemp = new Employee(1, "Bob", "java");
assertEquals(objectMapper.writeValueAsString(expectedemp), returnedemp.getContentAsString());
}
}
Testcase failed with:
org.opentest4j.AssertionFailedError: expected: <{"id":1,"name":"Bob","tech":"java"}> but was: <{"id":0,"name":"Bob","tech":"java"}>
at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
at org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1124)
at com.easytocourse.demo.RestServicewithActuatorApplicationTests.testaddemp(RestServicewithActuatorApplicationTests.java:55)
when i use #Mock or #SpyBean it is returning expected employee object
Please help me in understanding why #MockBean is not working?
Please clarify the below points
1. what is the difference between #Mock, #MockBean, #SpyBean, #InjectMock annotations, when to use these annotations?
#MockBean is a feature of Spring Boot Test.
Thus you need to remove the #RunWith(MockitoJUnitRunner.class) declaration which is using Mockito's JUnit 4 Runner.
If you want to use JUnit 4, you must use the SpringRunner via #RunWith(SpringRunner.class).
If you want to use JUnit Jupiter (part of JUnit 5), you'll need to use the SpringExtension via #ExtendWith(SpringExtension.class).
Depending on the version of Spring Boot Test that you are using, you may be able to exclude the #ExtendWith(SpringExtension.class) declaration, since recent versions of #SpringBootTest automatically register the SpringExtension for you.
Related topic: Difference between #Mock, #MockBean and Mockito.mock()
I am writing the unit testing for my controller and am getting 404 instead of 200 success.
When I search on the console it is showing we WARN log like
WARN [2021-02-12T11:53:27.711+0530] servlet.PageNotFound ||${fallback:user}|No mapping for GET /person%E2%80%8B/contacts%E2%80%8B/details
Why mapping changed to /person%E2%80%8B/contacts%E2%80%8B/details instead of /person/contacts/details
Test Class
#RunWith(MockitoJUnitRunner.class)
#ActiveProfiles("test")
#SpringBootTest
public class PersonControllerTest {
#Autowired
private MockMvc mockMvc;
#InjectMocks
private PersonController personController;
#Mock
private PersonService personService;
#Before
public void setupMethod() {
mockMvc = MockMvcBuilders.standaloneSetup(personController).build();
}
#Test
public void getPersonContactDetailTest() throws Exception {
Person person = new Person();
person.setName("Test");
Mockito.when(personService.getPersonContactDetail()).thenReturn(person);
RequestBuilder rqBuilder = MockMvcRequestBuilders.get("/person/contacts/details")
.accept(MediaType.APPLICATION_JSON);
mockMvc.perform(rqBuilder).andExpect(MockMvcResultMatchers.status().isOk());
Mockito.verify(personService, times(1)).getPersonContactDetail());
}
}
%E2%80%8B is the code for a "ZERO-WIDTH SPACE" character.
It has probably been inserted without you noticing it somehow. I recommend completely removing the string where the URL is defined and re-write it.
I try to test a #RestController within a integration test suite using MockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class WebControllerIT {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}
The #RestController (WebController) calls an injected #Service (RestClientService) which uses RestTemplate to call another REST server. This leads to the following error when running the test.
org.springframework.web.client.ResourceAccessException: I/O error on
GET request for "http://test123.com/42/status": test123.com; nested
exception is java.net.UnknownHostException: test123.com
I used MockRestServiceServer for the integration test of the #Service itself but have no idea how to archieve this within the test of #RestController.
How can I simulate a correct REST call of the RestTemplate?
The #RestController class.
#RestController
public class WebController {
private final RestClientService service;
#Autowired
public WebController(RestClientService service) {this.service = service;}
#GetMapping("/status")
public String getStatus() {
// extract pid from database ...
int pid = 42;
return this.service.getStatus(42);
}
}
The #Serviceclass.
#Service
public class RestClientService {
private final RestTemplate restTemplate;
public RestClientService(RestTemplate restTemplate) {this.restTemplate = restTemplate;}
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
}
Integration/Unit testing doesn't work that way.Objective of this kind of testing is to run through your code and make sure all the business requirement are met but not to hit other system or DB.Here in your case u shouldn't be hitting test123.com to get back data.What needs to done here is that you should mock that method.
public String getStatus(int pid) {
String url = String.format("http://test123.com/%d/status", pid);
return this.restTemplate.getForObject(url, String.class);
}
So that control doesn't enter this method but return you back the mock data(Dummy data).
For example let say that there are 2 kind of status this method is returning and you need to do some business validation based on the string returned.In this case u need to write 2 integration test and make sure the mocking method returns 2 different value(Dummy value instead of hitting that end point)
Reason why we are writing unit testing/integration testing is to make sure your entire code is working as expected but not to hit other system from ur code.
If you want to only test your controller layer, you would do like this.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MockServletContext.class)
#WebAppConfiguration
public class WebControllerIT {
private MockMvc mockMvc;
private RestClientService service
#Mock
private RestTemplate restTemplate
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
service = new RestClientService(restTemplate);
WebController webController = new WebController(service);
mvc = MockMvcBuilders.standaloneSetup(webController).build();
}
#Test
public void getStatusReurnsSomething() throws Exception {
//Mock the behaviour of restTemplate.
doReturn("someString").when(restTemplate).getForObject(anyString(), anyString());
this.mockMvc.perform(get("/status")).andExpect(status().isOk());
}
}