How to dynamically load different class for JUnit tests? - spring

I've written a couple of JUnit tests to test my REST functionality. Since I only want to test REST (and not the database, domain logic, ..) I made a stub filles with dummy data which stands for the rest of the backend.
[EDIT] For example I want to test /customers/all
A GET request will be responded to with an arraylist containing all names.
I therefore use MockMV.
this.mockMvc.perform(get("/customers/all").accept("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$[0].name", is("John")));
When you normally perform a GET request towards /customers/all a request will be sent to the database. Now, to test my REST controller I made a stub which responds to GET /customers/all with a simple arraylist containing just my name (as you can see in the test). When I test this local I simply replace the real class with this stub. How is this done dynamically?

I don't think your approach is the good one. Just use your real controller, but stub its dependencies (using Mockito, for example), just like you would do for a traditional unit test.
Once you have an instance of the controller using stubbed dependencies, you can use a standalone setup and use MockMvc to test, in addition to the controller code, the mapping annotations, the JSON marshalling, etc.
Thias approach is described in the documentation.
Example using Mockito, assuming the controller delegates to a CustomerService:
public class CustomerControllerTest {
#Mock
private CustomerService mockCustomerService;
#InjectMocks
private CustomerController controller;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void shouldListCustomers() {
when(mockCustomerService.list()).thenReturn(
Arrays.asList(new Customer("John"),
new Customer("Alice")));
this.mockMvc.perform(get("/customers").accept("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$[0].name", is("John")));
}
}

Related

Unit Testing Spring Boot API RESTful endpoints generated by Open API 3 YAML files

I have an application that is using Spring Boot (latest version) and creating a back-end that has RESTful api's. Traditionally, I have created controllers like:
#RestController
#RequestMapping("/contacts")
public class ContactController {
#Autowired
private ContactService service;
#RequestMapping(value = "/contactId/{contactId}",
method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ContactEntity getContactById(#PathVariable("contactId") long contactId) {
ContactEntity contactEntity = service.getContactById(contactId);
return contactEntity;
}
And an integrated test has always been like:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = ServiceContextConfiguration.class)
#ComponentScan("com.tomholmes.springboot.phonebook.server")
#Transactional
#WebAppConfiguration
public class ContactControllerTest {
#Test
public void testGetContactById() throws Exception {
MockHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders.get(BASE_URL + "/contactId/6");
this.mockMvc.perform(requestBuilder)
.andDo(print())
.andExpect(status().isOk());
}
}
This has always worked normally for years as a 'code first' api. Now, I am dealing with a contract-first API using OpenAPI 3 and a YAML file. The API is generated in the same location as before, and I would expect the test to work as it did before, but it does not.
So one resource:
[https://www.hascode.com/2018/08/testing-openapi-swagger-schema-compliance-with-java-junit-and-assertj-swagger/#API_Test]
is suggesting I use the assertj-swagger for the OpenAPI / Swagger contract testing.
Is this the only way to go? Is there no way for me to use my old traditional testing which I find extremely useful as an integrated test.
There is a third method I am also looking into:
[https://www.testcontainers.org/modules/mockserver/]
Which I am going to try also, and I am sure it will work.
I'm also wondering if there is code out there to auto-generate the Test just like there is to generate the API endpoint and the model, it would make sense if the Open API 3 also had the ability to generate the test was well.
Ultimately, I'd like to use my old way of testing if I could, but if not, then I'll try the other ways.
We had the same issue after switching to open api contract first with auto-generated controllers/delegate interfaces. The fix was to import the delegate impl in addition to the controller itself. Example:
#WebMvcTest(FeatureController.class)
#Import(FeatureDelegateImpl.class)
public class FeatureContractTest {
#Autowired
private MockMvc mvc;
#MockBean
private BusinessService businessService;
...
FeatureController is the controller generated by open-api, which typically will be in a generated-sources folder within target. FeatureDelegateImpl is our own implementation of the delegate interface, which lives in the src/main folder.

Is Injecting Repository dependency in Integration Testing is bad practice

I am developing REST CURD API using Spring Boot and following TDD. I am starting with READ operation to test the READ works I have to prepopulate data in my DB. For this purpose I am using H2 DB and created my Repository and using this Repository in my Integration testing Setup step to populate the data before my GET actual data being executed. My college told me its bad practice using Repository in integration testing to create data for this GET operation test instead he suggested to call POST API endpoint in Setup step and Execute GET test also implement PUT and DELETE IT tests.
My question is
Is injecting actual repository for purpose of data setup in Integration test class is wrong?
Can we use/call and Endpoint (POST) in setup or in GET testcase before actual Test logic being executed?
Can we call POST endpoint in init/setup and populate the data instead using Repository and implement GET,PUT,DELETE tests using POST data? Is advisable calling direct POST endpoint in setup for purpose of testing another endpoint in Test class and using #TestOrder annotation on class level.
Case 1
#SpringBootTest
#AutoConfigureMockMvc
public class CURDTest() {
#Autowired
private TestMovieRepository testMovieRepository;
#BeforeEach
void init() {
Movie myMovie = new Movie("test");
testMovieRepository.save(myMovie);
}
#Test
void getMovie_whenReturnsMovie(){
//Test logic using mockMvc;
}
}
Case 2:
#SpringBootTest
#AutoConfigureMockMvc
public class CURDTest() {
#BeforeEach
void init() {
//call POST Endpoint usin MockMvc populate data in table
}
#Test
void getMovie_whenReturnsMovie(){
//Test logic using mockMvc;
}
}
Case 3:
#SpringBootTest
#AutoConfigureMockMvc
#TestOrder
public class CURDTest() {
#BeforeEach
void init() {
//call POST Endpoint usin MockMvc populate data in table
}
#Test
#Order(1)
void getMovie_whenReturnsMovie(){
//Test logic using mockMvc;
}
#Test
#Order(2)
void updateMovie_whenReturnsMovie(){
//Test logic using mockMvc;
}
}

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.

Spring MockMvc not taking roles into account

I have API endpoints which require a user to hold a specific role. Therefore, in some of my tests I attempt to reach these endpoints and expect a 401 error, however I get 200. I am using MockMvc to perform the calls.
The following are some snippets of the controller class with one of the methods that I am testing:
#RestController
public class MyController {
#GetMapping("/getcurrentuser")
public User getCurrent() {
...code
}
}
The following is my test class (only showing the respective test method and variables):
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
#ContextConfiguration(classes = MyController.class)
public class MyControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testGetCurrentFailedDueToIncorrectRole() throws Exception {
mockMvc.perform(get("/api/getcurrentuser")
.with(user(USER_NAME).password(PASSWORD)))
.andExpect(status().isUnauthorized());
}
}
I have also have a spring security config class, however I'm not sure if it's being brought into context in this test (sorry I'm still fairly new to spring and unit testing). Inside this class I have the following line of code:
.antMatchers("/api/**").hasAnyRole("ADMIN", "READ_ONLY")
The test showed previously fails, as I said I get 200. Now at this point I think that I'm doing something wrong in the configuration of this test and that is why roles are not being accounted for. Or maybe I am confused on how the ".with" part works.
Any form of help would be appreciated.
If you are using Spring Boot, you might want to try using #SpringBootTest and #AutoConfigureMockMvc.
https://spring.io/guides/gs/testing-web/
Exact opposite problem (may be useful to go off of)

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

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.

Resources