Unit Testing of Spring MVC Controller: Failing to fully perform request - spring

I have a simple controller defined as below:
#Controller
#RequestMapping("/rest/tests")
public class TestController {
#Autowired
private ITestService testService;
#RequestMapping(value="/{id}", method=RequestMethod.DELETE)
#ResponseStatus(value = HttpStatus.OK)
public void delete(#PathVariable Integer id)
{
Test test = testService.getById(id);
testService.delete(test);
}
}
I have been trying to test the delete method, and have not succeeded so far. The test I have written is pretty simple too.
public class MockmvcTest {
#InjectMocks
private TestController test;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(test).build();
}
#Test
public void myTest() throws Exception {
this.mockMvc.perform(delete("/rest/tests/{id}", new Integer(4)))
.andExpect(status().isOk()
}
}
I have tested the method using "advanced rest client" extension in chrome and it works as expected.
By working I mean that the entity with the given id is deleted from database.
When myTest() is executed the status code is still 200, but the entity is not removed from the database.
What might be the reason behind this behavior?

You're using Mockito to inject mock service beans into your TestController (in particular, ITestService). All mocks by definition have no behaviour until you specify it, by default all operations you perform will either do nothing or return null. You can easily confirm that by setting a breakpoint inside TestController.delete method, executing the test in debug mode and inspecting values of test and testService variables.
Mockito is used for unit-level tests that replace SUT's collaborators with a mock that you set up to behave in a certain verifiable way. Once you call a method on your SUT (in your case that's TestController) you can assert whether it adheres to its contract or not.
It's actually a big no-no to allow your automated tests to modify a real instance of a database.

Related

Replace Mockito's when thenReturn implementation that was previously defined with new implementation

I am adding tests to a large codebase that is similar to this:
public class MyTests{
#Mock
private DBService dbService;
#Before
public void init(){
Mockito.when(dbService.getFromDb).thenReturn(getReturnResult());
}
}
Where getReturnResults() gets the some fake data. In 99% of cases this is the implementation I want, but when testing for exceptions I would like to do something like this:
#Test
public void useDifferentDBResults(){
Mockito.when(dbService.getFromDb).thenReturn(getDifferentReturnResult());
...
}
Where getDifferentReturnResult() gets some different data that will result in an error. I just need to replace the implementation for this one test.
Two things come to my mind:
Introduce in the same test class a second instance of DBService and use it "...when testing for exceptions":
#Mock
private DBService dbService;
#Mock
private DBService dbService2;
#Before
public void init() {
Mockito.when(dbService.getFromDb).thenReturn(getReturnResult());
Mockito.when(dbService2.getFromDb).thenReturn(getDifferentReturnResult());
}
...
#Test
public void useDifferentDBResults(){
// Use dbService2 here
...
}
or
Write a separate test class just for testing for exception(s) and move to that class useDifferentDBResults() and the respective test method(s).
If you want to dynamically provide the result of the mock then you could use the Answer interface of mockito. See this post for more details:
Dynamic return values with Mockito
Nevertheless I think in tests it is a best practice to repeat yourself in order to make each testmethod more readable. So my advice would be to move the mock initialization to each test method.

Is it possible to use an ErrorController with a #WebMvcTest?

I have an error controller (to handle the path "/error") that works when the application is running, but when in an unit test with #WebMvcTest it does not work.
The error controller also works if I send a request direct to its path.
This is my error controller:
#Controller
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
#RequestMapping("/error")
public ResponseEntity<List<Error>> handleError(HttpServletRequest request) {
and this is my test class:
#RunWith(SpringRunner.class)
#WebMvcTest({ErrorController.class})
public class ErrorControllerTest {
#Autowired
private MockMvc mockMvc;
I have also tried to add the class ErrorMvcAutoConfiguration in the WebMvcTest annotation.
I have debugged the ErrorMvcAutoConfiguration class and found that when the application is running it finds an error view resolver, but when running as unit test it does not find any.
The idea behind this test it to make sure the Spring configuration (which is code) that leads to the execution of this error controller is correct.
You are not performing a
unit test
if you expect to trigger the ErrorController by calling a method in MyController.
That is an integration test and you don't need to do it.
If you describe your test as this: "I want to see that an exception from the MyController class lands in the ErrorController class" then you are not testing either the MyController class or the ErrorController class;
instead, you are testing that Spring works.
You don't need to test that Spring works.
The authors of Spring test to see that Spring works.
Iff you want to test that the annotation in the ErrorController is correct such that Spring defines it as you ErrorController class as the error controller,
then you are on the correct path.
That is still an integration test and not a unit test.
To unit test the ErrorController class,
just create an instance of the ErrorController class in a unit test and call the methods.
You can use reflection in the unit test to verify that the annotations
"appear correct to you".
Here is a simple example ErrorController unit test.
public TestErrorController
{
private ErrorController classToTest;
#Mock
private HttpServletRequest mockHttpServletRequest;
private
#Before
public void beforeTest()
{
MockitoAnnotations.initMocks(this);
classToTest = new ErrorController();
}
#Test
public void handleError_allGood_success()
{
private ResponseEntity<List<Error>> actualResult;
// mock the desired mockHttpServletRequest functionality.
doReturn(something).when(mockHttpServletRequest).someMethod();
// Perform the test.
actualResult = classToTest.handleError(mockHttpServletRequest);
// Do asserts on the actualResult here.
}
}

SpringBoot: Testing the Service layer

Let's assume that I have two classes:
TodoRepository
TodoService
the TodoRepository is a simple CRUD Repository:
public interface TodoRepository extends CrudRepository<T, ID> {
}
the TodoService is just a class which calls this Repository:
#Service
public class TodoService{
private final TodoRepository todoRepository;
#Autowired
public TodoService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
public void createTodo(Todo todo) {
todoRepository.save(todo);
}
}
Should I bother testing the service layer?
Edit:
Thanks to the explanation from #Dherik. I created a test class which looks like:
Note: I am using JUnit5, Mockito and Spring framework
#ExtendWith(SpringExtension.class)
class TodoServiceTest {
#MockBean
private TodoRepository todoRepository;
private TodoService todoService;
#BeforeEach
void setUp() {
todoService = new TodoService(todoRepository);
}
#AfterEach
void tearDown() {
clearInvocations(tanklevelRepository);
}
#Test
public void createTodo() {
todoService.createTodo(new Todo());
// verify if the save method is called when createTodo is called too
verify(todoRepository, times(1)).save(any(Todo.class));
}
}
Yes, it's important.
Even now being a very simple class, maybe some developer on the future could add some weird condition on this method createTodo that the Todo is not saved anymore.
If you write a test for the actual method to verify if the save is called, the developer will be advised about the situation if he makes some change that affect the Todo save.
See a pseudo test example:
#Test
public void createTodo() {
TodoRepository todoRepository = mock(TodoRepository.class);
TodoService todoService = new TodoService(todoRepository);
todoService.createTodo(new Todo());
// verify if the save method is called when createTodo is called too
verify(todoRepository, times(1)).save(any(Todo.class));
}
I've seen this kind of thing tested with Junit using a Mock framework and injecting a mock repo into the service, then checking the mock repo was called. That seems really pointless to me as the test knows too much about the implementation. If you change the implementation you have to rewrite the test, so it's no use for refactoring.
I would test this kind of thing with an integration test that treated the app like a black box. i.e. start the app, trigger whatever creates a todo and check that it was created. I'd probably use cucumber-jvm and have a scenario with a step to create a todo and another to retrieve it.
I think you should create tests that
help you write reliable code
allow refactoring without the need to rewrite tests
prove the application does what it's supposed to

Controller layer test in SpringBoot application

I have a controller in my SpringBoot app:
#Controller
#RequestMapping("/v1/item")
public class Controller{
#Autowired
private ServiceForController service;
#PostMapping()
public String createItem(#ModelAttribute Item item) {
Item i = service.createItem(item.getName(), item.getDomain());
return "item-result";
}
}
And I'd like to test it separately from service with a help of mocks.How to implement it?
There are at least two approaches to do it:
To start up the whole SpringBoot context and make a sort of integration tests
Example:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
private MockMvc mvc;
#Test
#WithMockUser(roles = "ADMIN")
public void createItem() throws Exception {
mvc.perform(post("/v1/item/")
.param("name", "item")
.param("domain", "dummy.url.com"))
.andExpect(status().isOk());
//check result logic
}
Test exclusive controller layer and limit the whole loaded context exclusively to it. Example:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = Controller.class)
public class ControllerTest{
#Autowired
private MockMvc mvc;
#MockBean
private ServiceForController service;
//testing methods and their logic
...
}
Even though the second approach seems more sensible (as for me) in terms of resources used, it may cause plenty of inconveniences due to the lack of beans initialized. For instance, before I decided to try another option, I faced the need to create mocks of at least 5 beans that are added to the context on SpringBoot start in my ContollerTest class.
Thus, I had to switch to the approach with a use of #SpringBootTest in combination with #SpyBean, that allowed me to call a Mockito verify() method.

How to call Springs service method from controller (Junit)

I have seen example , how to call spring controller using mockito.
Using Mock I call Spring MVC controller.
Controller Invokes Spring service class.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class TestController {
#Mock
private TestService testService;
#InjectMocks
private PaymentTransactionController paymentController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
}
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
}
Ok it works well. I calls my Spring controller very well. But In Spring controller I have Injected Service Layer.
#Autowired
private TestService serviceTest;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
serviceTest.save();
// in save method I call dao and dao perist data;
// I have injected dao intrface in serviceTest layer
...
return result;
}
The problem is that, my app does not invokes save method, it is not entered in it. I have no error too. The same result is when I call save() method from Junit (I have commented it in test() method).
When I debug, I have seen that interrupt method happens of org.mockito.internal.creation.MethodInterceptorFilter
How to solve this problem? what happens?
If you are doing a unit test of your controller, you should mock the service layer (what you are doing). In this kind of test, you just control that :
the correct methods of the controller are triggered and they produce what is expected
the correct methods in service layer are called ... in the mock
You simply have to configure the return values of the methods of the mock (if relevant), or control what was called
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
// set return values from the mocked service
when(testService.find(1)).thenReturn(...);
}
and verify later what has been called
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
verify(testService, times(1)).save();
}
If you want to do an integration test, you do not mock the service, but setup an application context to inject real beans, but ordinarily use an embedded database instead of the real one.
just change #InjectMocks to #Autowired. This fix the issue! In this case you are not mocking, you are invoking method with real data.
As I understand, you perform post to "/tr/test" resource, but request mapping in your controller is '/payment'. Make sure you post to resource mapped in the controller.

Resources