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;
}
}
Related
I have the test below:
#AutoConfigureMockMvc
#SpringBootTest
public class ProjectDashboardTests {
#Autowired
private MockMvc mockMvc;
#Test
public void projectsDashboardShows() throws Exception {
this.mockMvc
.perform(get("/projects/1"))
.andDo(print())
.andExpect(status().isOk());
}
#Test
public void projectFormShows() throws Exception {
this.mockMvc
.perform(get("/get-project-form/1"))
.andDo(print())
.andExpect(status().isOk());
}
These rely upon path variables 1 = user ID. For these tests to work correctly there must a user account with user id = 1 which is currently logged in. How would I simulate that in mockMVC?
You have few options, depends which test you want to create and which configuration you have (more detail is needed to answer more accurately).
For unit tests, you can mock your repository (or service), and method in which you get the Project. For example if you have ProjectService with method getProjectById(Long id), you can use something like that:
#AutoConfigureMockMvc
#SpringBootTest
public class ProjectDashboardTests {
...
#MockBean
private ProjectService service; // <- mock your service
#Test
public void projectsDashboardShows() throws Exception {
// mock service method in selected test or in #BeforeEach method
Mockito.when(service.getProjectById(ArgumentMatchers.anyLong())
.thenReturn(new Project(/* your own test object*/));
...
}
...
}
Based on For these tests to work correctly there must a user account with user id = 1 which is currently logged in, I assume you work with Spring Security? You can take a look at the #WithMockUser annotation if you want simulate selected user.
For integration tests, you should prepare database (H2 for example) for tests only, before each test add project into db and remove after each test (run independently).
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)
I am trying to write an integration test using Spring Boot that tests the transaction logic in one of my controllers.
What the test should do, is the following:
Inject one of my controllers using #Inject
Replace an email dependency in the controllers dependencies with a Mock, to avoid actually sending an email during the integration test.
Call a method of the controller
Assert that the transactions of the called method are properly rolled back when the mail sending mock throws an exception.
Now my problem is that, when the test runs, the controller is injected into my test class but all its dependencies are null. Here is my integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#IntegrationTest
#SpringApplicationConfiguration(App.class)
#WebIntegrationTest
public MyIntegrationTest () {
#Inject MyController controller;
#Before
public void before () {
// replace one particular dependency of controller with a mock
}
#Test
public void testFoo () { ... }
}
Due to the test being an integration test which starts up a full spring web application context, I was expecting that my controller would have all its dependencies already autowired, but that is obviously not the case and instead all dependencies are set to null.
Question: Do I need to use some additional annotations, or setup something in my #Before method? Or am I approaching the problem from a completely wrong side?
Update: Is it possible to test my Spring MVC Layer, without testing via HTTP such as with TestRestTemplate or MockMvc? But by directly
Test with TestRestTemplate instead of injecting the controller itself. Controllers is obviously a spring bean but if you directly inject it in your test class, it wont be able to initialize the context.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleStart.class)
public class ExampleTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void exampleTest() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
ExampleStart.java -> The spring boot starter class
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class ExampleStart extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExampleStart.class);
}
public static void main(String[] args) {
SpringApplication.run(ExampleStart.class, args);
}
}
Ref : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
But if you want to test service method, you can use #Autowired and call the methods as usual.
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")));
}
}
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.