How to write Unit Test for RESTApiController – Beginners guide
Kategorien API, Functional Testing, Java, Junit, Spring, Unit Testing
I recently wrote an Overview article about Software Testing now I´m going more specific about the methods of Functional Testing.
I´m starting with Unit Testing. Aim of this blog post is to write an functional Unit test for an RESTApiController.
I did manage to write an Unit test yesterday and I thought why not sharing my process of learning how to write Unit tests with you, so here you go.
But first let´s clear what Unit Testing is.
What is Unit Testing?
Unit testing is usually performed by the developer who writes different code units. This type of test involves breaking your programm into pieces, and subjecting each piece to a series of tests.
When should you perform unit tests?
They should be done as often as possible. When you are performing tests as part of the development process, your code is automatically going to be designed better than if you just wrote the functions and then moved on.
Also, concepts such as Dependency Injection are going to evolve naturally into your code.
The most obvious benefit is knowing down the road that when a change is made, no other individual units of code were affected by it if they all pass the tests.
How to perform a Unit Test for an RESTController ?
So my RestController looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package com.benny.APIController; import com.benny.Entity.Test; import com.benny.Service.TestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; @RestController public class TestAPIController { @Autowired private TestService testService; @RequestMapping(value = "/tests/get/{test_id}", method = RequestMethod.GET) public ArrayList<Test> getTest(@PathVariable("test_id") Long testID){ return testService.getTest(testID); } @RequestMapping(value = "/tests/getAllTests", method = RequestMethod.GET) public ArrayList<Test> getTests(){ return testService.getTests(); } @RequestMapping(value = "/test/save", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public void saveTest(@RequestBody Test test){ testService.saveTest(test); } @RequestMapping(value = "/test/update", method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_JSON_VALUE) public void updateTest(@RequestBody Test test){ testService.updateTest(test); } @RequestMapping(value = "/test/delete/{test_id}", method = RequestMethod.DELETE) public void deleteTest(@PathVariable("test_id") Long id){ testService.deleteTest(id); } } |
I´m trying to write Unit Tests for each Method of the Controller. I´m doing that with Junit.
My Controller has following RequestMethods
- GET
- POST
- PATCH
- DELETE
At first I´m trying to explain to you what Annotation you need to create a Unit Test for Spring.
@RunWith(SpringRunner.class)
The Annotation is uses to configure a unit test that required Spring dependency injection
In order for the unit test to run a batch job, the framework must load the job’s ApplicationContext. Two annotations are used to trigger this:
@RunWith(SpringJUnit4ClassRunner.class): Indicates that the class should use Spring’s JUnit facilities.
@ContextConfiguration(locations = {…}): Indicates which XML files contain the ApplicationContext.
@WebMvcTest(Controller you want to test.class)
Can be used when a test focuses only on Spring MVC components.
@Autowired
The @Autowired annotation tells Spring where an injection needs to occur.
@Mockbean
Annotation that can be used to add mocks to a Spring ApplicationContext
@Before
When writing tests, it is common to find that several tests need similar objects created before they can run. Annotating a public void method with @Before causes that method to be run before the Test method.
@Test
The Test annotation tells JUnit that the public void method to which it is attached can be run as a test case.
What is MockMvc?
MockMvc is the main entry point for server-side Spring MVC test support. Perform a request and return a type that allows chaining further actions, such as asserting expectations, on the result.
Now lets move on with creating the Testclass for the unit test. My test class is located in the same package as the ApiControllers.
Just create new new Test class called TestApiControllerTest. Now let´s setup the test with the above mentioned Annotations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
package com.benny.APIController.ApiControllerTest; import com.benny.APIController.TestAPIController; import com.benny.Service.TestService; import static org.hamcrest.Matchers.*; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import java.util.ArrayList; @RunWith(SpringJUnit4ClassRunner.class) @WebMvcTest(TestAPIController.class) public class TestApiControllerTest { @Autowired // The @Autowired annotation tells Spring where an injection needs to occur. private MockMvc mockMvc; @Autowired private ObjectMapper mapper; @MockBean // Annotation that can be used to add mocks to a Spring ApplicationContext private TestService testService; com.benny.Entity.Test first = new com.benny.Entity.Test(); com.benny.Entity.Test second = new com.benny.Entity.Test(); com.benny.Entity.Test third = new com.benny.Entity.Test(); @Before public void setup() { first.setId(1L); first.setFirstname("Moritz"); first.setLastname("Vogt"); second.setId(2L); second.setFirstname("Benjamin"); second.setLastname("Haid"); third.setId(3L); third.setFirstname("Mehmet"); third.setLastname("Yildiz"); } @Test // The Test annotation tells JUnit that the public void method to which it is attached can be run as a test case. public void getAllTests() throws Exception { ArrayList<com.benny.Entity.Test> allTests = new ArrayList<com.benny.Entity.Test>(); allTests.add(first); allTests.add(second); allTests.add(third); given(testService.getTests()).willReturn((ArrayList<com.benny.Entity.Test>) allTests); mockMvc.perform(MockMvcRequestBuilders.get("/tests/getAllTests")) .andExpect(status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$", hasSize(3))) .andExpect(jsonPath("$[0].firstname", is(first.getFirstname()))) .andExpect(jsonPath("$[0].lastname", is(first.getLastname()))) .andExpect(jsonPath("$[1].firstname", is(second.getFirstname()))) .andExpect(jsonPath("$[1].lastname", is(second.getLastname()))) .andExpect(jsonPath("$[2].firstname", is(third.getFirstname()))) .andExpect(jsonPath("$[2].lastname", is(third.getLastname()))); } @Test public void getTestById() throws Exception{ ArrayList<com.benny.Entity.Test> allTests = new ArrayList<com.benny.Entity.Test>(); allTests.add(first); given(testService.getTest(first.getId())).willReturn((ArrayList< com.benny.Entity.Test >) allTests); mockMvc.perform(MockMvcRequestBuilders.get("/tests/get/{test_id}", first.getId())) .andExpect(status().isOk()) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$", hasSize(1) )) .andExpect(jsonPath("$[0].firstname", is(first.getFirstname()))) .andExpect(jsonPath("$[0].lastname", is(first.getLastname()))); } @Test public void saveTest() throws Exception { String json = mapper.writeValueAsString(first); mockMvc.perform(MockMvcRequestBuilders.post("/test/save") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(json) .accept(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()); } @Test public void updateTest() throws Exception { first.setFirstname("Joel"); String json = mapper.writeValueAsString(first); mockMvc.perform(MockMvcRequestBuilders.patch("/test/update") .contentType(MediaType.APPLICATION_JSON_VALUE) .content(json) .accept(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()); } @Test public void deleteTest () throws Exception { mockMvc.perform(MockMvcRequestBuilders.delete("/test/delete/{test_id}", third.getId()) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); } } |