Method mocked remains for other tests as well - java

I am writing JUnits for my Spring controller. In one of the cases, I am testing the Exception catched within the controller. I have mocked the service to throw a RuntimeException. Other test checks the successful output.
Individually both tests work, but when I execute both together, if the first test executes first, then the second test too throw RuntimeException. Is there anything that I need to deregister the mocked method?
(Please ignore the syntax)
class UserController {
#Autowired
UserService service;
#RequestMapping("...")
public ResponseEntity getUser(){
try{
User user = service.getUserAttributes();
return new ResponseEntity(user, HttpStatus.OK);
}
catch(Exception e){
return new ResponseEntity("Eror", HttpStatus.BAD_REQUEST);
}
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes=TestConfig.class)
public class UserControllerDocumentation {
#Autowired
private WebApplicationContext webApplicationContext;
private RestDocumentationResultHandler document;
private MockMvc mockMvc;
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
#Autowired
UserService userService;
#Before
public void setUp(){
this.document = document("{method-name}", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.apply(documentationConfiguration(this.restDocumentation)).alwaysDo(document)
.build();
}
#Test
public void textError() throws Exception {
when(userService.getUserAttributes(anyInt())).thenThrow(new RuntimeException());
this.mockMvc.perform(get("/user/xxx")
.accept("application/xml"))
.andExpect(status().isBadRequest());
}
#Test
public void textUser() throws Exception {
when(userService.getUserAttributes(anyInt())).thenReturn(mockUserObj);
this.mockMvc.perform(get("/user/10")
.accept("application/xml"))
.andExpect(status().isOk());
}

Maybe you should reset mocked object before you run each test.
You can do it using reset method.
Here are more details
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#17
To run this before each class you can use #Before annotation.

I don't know how #Autowired works, but obviously: your class has one instance of userService; and your textError() testcase configures that to throw an exception. And when your second test runs, that "behavior specification" is still in place and throws on you.
Thus, I see two options:
a) you use your "after" or "before" methods to reset the userService
b) each test method gets its own userService

Turns out that I need to return something for the mocked method even after throwing an exception. Added thenReturn(null) after thenThrow
#Test
public void textError() throws Exception {
when(userService.getUserAttributes(anyInt())).thenThrow(new RuntimeException()).thenReturn(null);
this.mockMvc.perform(get("/user/xxx")
.accept("application/xml"))
.andExpect(status().isBadRequest());
}

Related

My service test fails when I have an if statement in the class under test SpringBoot

I am writing tests from my springboot application. The class has a method getUserById which returns Optional<User>. This methos has an if statement that will check whether an row was returned from repository before sending a response.
Problem:
With the if statement in place, my test always throws the error in the if statement. when I remove the if statement, the test passes. What am I missing?
This is my UserServiceImpl (Class under test)
#Service
#RequiredArgsConstructor
#Transactional
#Slf4j
public class UserServiceImpl implements UserService, UserDetailsService {
#Autowired
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Override
public List<User> getUsers() {
log.info("Fetching users");
return userRepository.findAll();
}
#Override
public Optional<User> getUserById(Long id) {
log.info("Fetching user id: {}", id);
Optional<User> user = userRepository.findById(id);
if (!user.isPresent()) {
throw new ResourceNotFoundException(MessageUtil.ERROR_USER_NOTFOUND);
}
return user;
}
}
This is my UserServiceImplTest (test class)
#RunWith(SpringRunner.class)
#SpringBootTest
class UserServiceImplTest {
#MockBean
private UserRepository userRepositoryTest;
#InjectMocks
private UserServiceImpl userServiceTest;
#Mock
private PasswordEncoder passwordEncoder;
private List<User> userSet;
private User user1;
private User user2;
#BeforeEach
void setUp() {
userServiceTest = new UserServiceImpl(userRepositoryTest, passwordEncoder);
Set<ApplicationUserRole> roles = new HashSet<>();
roles.add(ApplicationUserRole.TEST_USER);
userSet = new ArrayList<>();
user1 = User.builder().nickname("test-nickname")
.id(1L)
.username("254701234567")
.roles(roles)
.password("password")
.build();
user2 = User.builder().nickname("test2-nickname2")
.id(2L)
.username("254701234589")
.roles(roles)
.password("password")
.build();
userSet.add(user1);
userSet.add(user2);
userSet.stream().forEach(user -> {
userServiceTest.saveUser(user);
});
}
#AfterEach
void tearDown() {
}
#Test
void testGetUsers() {
when(userServiceTest.getUsers()).thenReturn(userSet);
assertEquals(2, userServiceTest.getUsers().size());
verify(userRepositoryTest).findAll();
}
#Test
void testGetUserById() {
when(userServiceTest.getUserById(user1.getId())).thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
#Test
void testSaveUser() {
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals(1L, user1.getId());
verify(userRepositoryTest).save(user1);
}
#Test
void updateUser() {
user1.setNickname("nickname-update");
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals("nickname-update", user1.getNickname());
verify(userRepositoryTest).save(user1);
}
}
NOTE: Other tests work just fine
None of your tests set up the repository mock. You are trying to mock the service method instead, which will implicitly call the real method while mocking. But the service method is never called to assert correct behavior. In other words: your service's behavior is never exercised by the test, because the return value of the method calls is overwritten.
Example:
#Test
void testGetUsers() {
// repository is never mocked
// vvvvvvvvvvvvvvvvvvvvvvvvvv--- this calls the service method
when(userServiceTest.getUsers()).thenReturn(userSet);
// ^^^^^^^^^^^^^^^^^^^--- this overwrites the return value of the service method
assertEquals(2, userServiceTest.getUsers().size()); // this uses the overwritten return value
verify(userRepositoryTest).findAll();
}
To fix, you need to mock the repository (not the service) and then call the real service. It is also quite useless to assert the user's id, because the user is set up by the test, not in the classes under test.
#Test
void testGetUserById() {
// arrange
when(userRepositoryTest.getUserById(user1.getId())
.thenReturn(Optional.ofNullable(user1));
// act
Optional<User> userById = userServiceTest.getUserById(user1.getId());
// assert
assertEquals(1, user1.orElseThrow().getId());
verify(userRepositoryTest).findById(user1.getId());
}
I'd also question your usage of verify at the end of test. You are testing implementation details here. You should only be interested in the return value of your service, not which methods of the repository it is calling. Especially since you are mocking those methods anyway, so you already know with which arguments they are called; otherwise the mock would not return the configured return value in the first place.
A side-question is why your if-condition is always true and the exception always thrown. Again: incorrect/missing setup of your mocks.
#Test
void testGetUserById() {
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv--- calls real method, but the repository mock is not set up
when(userServiceTest.getUserById(user1.getId()))
.thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
You are trying to mock the service method call but the service is autowired — that doesn’t work. You can mock instead the repository method call since the repository is annotated as MockBean, that should work.

How to properly test a Spring Boot using Mockito

I never did a test and decided I would do my first.
I am using Spring Boot and by default the layout is as follows: https://zapodaj.net/c8b65f1644e95.png.html
The file that is created on startup looks like this
#RunWith(SpringRunner.class)
#SpringBootTest
public class RestWebServicesApplicationTests {
#Test
public void contextLoads() {
}
}
I do not change anything here, because I do not need it.
By contrast, I created a new class
public class CheckUserDataRestControllerTest {
#Mock
private UserService userService;
#InjectMocks
private CheckUserDataRestController checkUserDataRestController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(checkUserDataRestController).build();
}
#Test
public void textExistsUsername() throws Exception {
mockMvc
.perform(get("/checkUsernameAtRegistering")
.param("username", "jonki97"))
.andExpect(status().isOk());
}
}
To test the controller method, which should return the status OK.
#GetMapping("/checkUsernameAtRegistering")
public HttpEntity<Boolean> checkUsernameAtRegistering(#RequestParam String username) {
return ResponseEntity.ok().body(!userService.existsByUsername(username));
}
However, during the test, he throws me out
java.lang.AssertionError: Status
Expected :200
Actual :404
<Click to see difference>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
...
This is the tested controller: https://pastebin.com/MWW9YwiH
I did not make any special configurations because I did not find out that I needed to do something.
The error 404 means the Mock does not recognize your endpoint to test. The end point should be:
#Test
public void textExistsUsername() throws Exception {
mockMvc
.perform(get("/checkUserData/checkUsernameAtRegistering")
.param("username", "jonki97"))
.andExpect(status().isOk());
}
Hope this help.

Mockito and Java Spring - repository in tests

I am new to mockito / Java Spring and I tried to make a test. I have an admin controller, with this method in it :
#RequestMapping(value="/admin/users", method = RequestMethod.GET)
public ResponseEntity<List<User>>users(){
List<User> students=this.userService.getAll();
if(students.isEmpty())
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);
return new ResponseEntity<List<User>>(students,HttpStatus.OK);
}
Now, I tried to make a test, to see if it works, something like this :
public class AdminControllerTest {
#InjectMocks
private AdminController controller;
#InjectMocks
private UserServiceImpl userService = new UserServiceImpl();
#Mock
private UserRepository userRepository;
private MockMvc mockMvc;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() throws Exception {
User user = new User();
user.setId(5);
user.setActive(true);
user.setLastName("Test1");
user.setName("Test1");
user.setPassword("123123");
user.setRole(User.Role.student);
user.setEmail("test#gmail.com");
when(userService.save(user)).thenReturn(user);
userService.save(user);
mockMvc.perform(get("/admin/users")).andDo(print());
}
}
The problem is that I am not sure how to make the Test class add items to the repository. I tried it this way but I get NullPointerExceptions. I am guessing it is because the repository is empty and when the .getAll() method is called it returns the error.
Since you've mocked out the repository, you don't need to save things to it. All you need to do is specify what the repository would return when certain methods are called.
Instead of:
when(userService.save(user)).thenReturn(user);
... try using:
when(userRepository.findAll()).thenReturn(Collections.singletonList(user));
Why it didn't work
The reason you were getting NullPointerException was one of:
you are setting up a when on an object that isn't even a mock, and/or
you didn't provide a when for the userRepository.findAll() method, so it returned null when you called it:
List<User> students=this.userService.getAll(); <-- returned null
... which was then dereferenced and threw the NPE:
if(students.isEmpty()) <-- throws NPE when students is null

Mockito.when().thenReturn() doesn't work or returns null

During the test there is a NullPointerException thrown. I tried to debug it and the only thing I worked out was that eventOptional is always null. Just as if Mockito.when().thenReturn() didn't work. Can anybody help? Here's my code for a tested service and for the test itself:
#Service
public class EventService {
#Autowired
public EventService(EventRepository eventRepository) {
this.eventRepository = eventRepository;
}
//...
public void updateEvent(EventDTO eventDTO) {
Optional<Event> eventOptional = eventRepository.findOneById(eventDTO.getId());
eventOptional.orElseThrow(() -> new BadRequestException(EVENT_NOT_FOUND));
//...
}
}
And the test class:
#RunWith(MockitoJUnitRunner.class)
public class EventServiceTest {
#Mock
private EventRepository eventRepository;
#InjectMocks
private EventService eventService;
private Event sampleEventFromDb;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void shouldUpdateEventTestAndWithProperTime() throws Exception {
EventDTO eventDTOMock = Mockito.mock(EventDTO.class);
sampleEventFromDb = Event.builder()
.name("name")
.startDateTime(LocalDateTime.now())
.placeName("place")
.description("description")
.publicEvent(true)
.owner(new User())
.build();
Mockito.when(eventRepository.findOneById(anyString())).thenReturn(Optional.of(sampleEventFromDb));
Mockito.when(eventDTOMock.getId()).thenReturn("1");
eventService.updateEvent(eventDTOMock); //NullPointerException
//...
}
}
I got this same error, after trying a lot of things I fixed it by replacing the anyString() method with any()
Try this:
Mockito.when(eventRepository.findOneById(any())).thenReturn(Optional.of(sampleEventFromDb));
Looks like the problem is that initMock is called twice: once by the runner and once by the setUp method. Running the test with the regular runner or removing the initMocks call from the setUp method fixes this problem.
Change anyList(), anyMap()..... to any(). After long debugging it is worked finally.

Write JUnit test for #ExceptionHandler

I am writing a Rest service using Spring MVC. Here is the outline of the class:
#Controller
public class MyController{
#RequestMapping(..)
public void myMethod(...) throws NotAuthorizedException{...}
#ExceptionHandler(NotAuthorizedException.class)
#ResponseStatus(value=HttpStatus.UNAUTHORIZED, reason="blah")
public void handler(...){...}
}
I have written my unit tests using the design posted here. The test is basically as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(....)
public class mytest{
MockHttpServletRequest requestMock;
MockHttpServletResponse responseMock;
AnnotationMethodHandlerAdapter handlerAdapter;
#Before
public void setUp() {
requestMock = new MockHttpServletRequest();
requestMock.setContentType(MediaType.APPLICATION_JSON_VALUE);
requestMock.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
responseMock = new MockHttpServletResponse();
handlerAdapter = new AnnotationMethodHandlerAdapter();
}
#Test
public void testExceptionHandler(){
// setup ....
handlerAdapter.handle(...);
// verify
// I would like to do the following
assertThat(responseMock.getStatus(), is(HttpStatus.UNAUTHORIZED.value()));
}
}
However, the call to handle is throwing the NotAuthorizedException. I have read that this is by design to be able to unit test that the method throws the appropriate exception, however I would like to write an automated test that the framework is handling this exception appropriately and that the class under test has implemented the handler appropriately. Is there a way to do this?
Please be aware that I do not have access to the actual code in a place where I could post it.
Also, I am limited (for unfortunate reasons) to Spring 3.0.5 or 3.1.2.
Consider using Spring 3.2 and its mvc-test-framework
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml")
public class WebMvcTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(
get("/testx")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isUnauthorized());
}
}
Controller code
#Controller
public class MyController {
public class MyException extends RuntimeException {
};
#RequestMapping("/testx")
public void myMethod() {
throw new MyException();
}
#ExceptionHandler(MyException.class)
#ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "blah")
public void handler() {
System.out.println("handler processed");
}
}
This "test" passes well.
Disclaimer: currently I'm a noob in Spring MVC testing, actually it's my first test.
upd: Thanks to The Drake for the correction.
Annotate your Exception Handling controller with #ControllerAdvice instead of #Controller.
As Boris Treukhov noted when adding the #ExceptionHandler annotation to a method in the controller that throws the exception will make it work but only from that specific controller.
#ControllerAdvice will allow your exception handeling methods to be applicable for your whole application not just one specific controller.
You could change #Test to
#Test(expected=NotAuthorizedException.class)
This would return true if the internals throw up that exception and false otherwise.
This would also make the assertThat() unnecessary. You could write a second test that catches the NotAuthorizedException then you could inspect the responseMock under that condition then.

Categories