[Java] Testing Spring framework controller with Junit

3 minute read

Target audience

・Spring Framework beginner ・Junit beginner ・I want to test the Spring framework controller with Junit! !! !!

Project ver

・Springframework 4.1.6 ・Junit 4.1.2 ・Servlet 3.1.0 ・Hamcrest 2.2 ・Mockito 3.3.3

File structure

// Main layer
src.main.java
  |
  +--- jp.co.demo
        +--- controller
        +--- dto
        +--- form
        +--- entity
        +--- mapper
        +--- service
  
// test layer
src.test.java
  |
  +--- jp.co.demo
  | +--- controller
  |
src.test.resources
  |
  +--- META-INF
  | |
  | +--- sql
  | |
  | +--- insert_data.sql // SQL for creating test data
  |
  +--- spring
            |
            +--- mvc-config.xml //Bean definition file

test

SQL executable

insert_data.sql



DELETE FROM messages;
DELETE FROM users;

INSERT INTO users(id,account,password,name,branch_id,department_id) VALUES (1,'testuser','$2a$10$i0FlsKe6FiEuViMhclA90uCjCCKeLhtcswz01Rwl9qTIsIY1c.ohO','ALHtaro',1,1)

ALTER TABLE messages MODIFY id int not null; --Temporary auto increment reset to reset auto increment
ALTER TABLE messages AUTO_INCREMENT = 1; --Auto increment reset
ALTER TABLE messages MODIFY id int not null auto_increment; --Add auto increment

INSERT INTO messages(title,text,category,user_id, created_date) VALUES('title','text','category',1,'2020-05-27 01:02:03'); --3
INSERT INTO messages(title,text,category,user_id, created_date) VALUES('title','body','category',1,'2020-05-28 02:03:04'); --4
INSERT INTO messages(title,text,category,user_id, created_date) VALUES('title','body','category',1,'2020-05-23 03:04:05'); --1
INSERT INTO messages(title,text,category,user_id, created_date) VALUES('title','body','category',1,'2020-05-24 04:05:06'); --2
INSERT INTO messages(title,text,category,user_id, created_date) VALUES('Title','Body','Category',1,'2020-05-29 05:06:07'); --5

Controller test

DemoControllerTest.java



@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration // Preparing to use context
@ContextConfiguration(locations = "classpath:spring/mvc-config.xml") //Specify which Bean definition file to use
@Transactional // Defined to roll back after the test
public class DemoControllerTest {

private MockMvc mockMvc;

@Autowired
private WebApplicationContext wac; // prepare context

@Autowired
private JdbcTemplate jdbcTemplate;

@Before
public void setUp() {
executeScript("/META-INF/sql/insert_data.sql"); // Execute SQL
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}

@Test
public void test_1 Home screen display () throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/home")) // GET communication behavior with perform
.andDo(print()) // print to console
.andExpect(status().isOk()) // check status code
.andExpect(view().name("home")) //View is correct
.andExpect(model().attributeExists("allUserMessage")) // Check if the target model exists
.andExpect(model().attributeExists("userComment")) // Check if the target model exists
.andExpect(model().attributeExists("deleteCommentForm")) // Check if the target model exists
.andExpect(model().attributeExists("commentForm")) // Check if the target model exists
.andExpect(model().attributeExists("userMessageForm")) // Check if the target model exists
.andReturn(); // return the result to mvcResult

ModelAndView mav = mvcResult.getModelAndView();
int[] expectMessageIdList = {5, 2, 1, 4, 3 }; // for ascending order confirmation

@SuppressWarnings(value = "unchecked")
List<UserMessageForm> actualMessageList = ((List<UserMessageForm>) mav.getModel().get("allUserMessage"));

int expectMessageListSize = 5;
assertEquals(expectMessageListSize, actualMessageList.size()); // Does the created data match the List size of the displayed model?

for (int i = 0; i <5; i++) {
assertEquals(expectMessageIdList[i], actualMessageList.get(i).getId()); // Are the message contents in ascending order?
}

}

@Test
public void test_2_1 Can the login screen be displayed? () throws Exception {
mockMvc.perform(get("/login"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeExists("loginForm"));
}

@Test
public void test_2_2 Can you log in () throws Exception {
LoginForm loginForm = new LoginForm();
loginForm.setAccount("testuser");
loginForm.setPassword("password");

mockMvc.perform(post("/login").flashAttr("loginForm", loginForm))
.andDo(print())
.andExpect(view().name("redirect:/home")) // Transition destination when login is successful
.andExpect(status().isFound());
}

@Test
public void test_2_3 When the password is incorrect, an error is output and the screen transitions to the login screen () throws Exception {
LoginForm loginForm = new LoginForm();
loginForm.setAccount("testuser");
loginForm.setPassword("wrong password");

MvcResult mvcResult = mockMvc.perform(post("/login")
.flashAttr("loginForm", loginForm))
.andDo(print())
.andExpect(view().name("login"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("loginForm")) // Is there an error in Model
.andReturn();

ModelAndView mav = mvcResult.getModelAndView();
BindingResult result = (BindingResult) mav.getModel()
.get("org.springframework.validation.BindingResult.loginForm");

String actualErrorMessage = result.getFieldError("account").getDefaultMessage();
String expectErrorMessage = "Wrong login ID or password";
assertThat(expectErrorMessage, is(actualErrorMessage)); // Check error message
}

@Testpublic void test_2_4 When trying to log in with a non-existent account, output an error on the screen and transition to the login screen () throws Exception {
LoginForm loginForm = new LoginForm();
loginForm.setAccount("non-existent account");
loginForm.setPassword("password");

MvcResult mvcResult = mockMvc.perform(post("/login")
.flashAttr("loginForm", loginForm))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("loginForm")) // Is there an error in Model
.andReturn();

ModelAndView mav = mvcResult.getModelAndView();
BindingResult result = (BindingResult) mav.getModel()
.get("org.springframework.validation.BindingResult.loginForm");

String actualErrorMessage = result.getFieldError("account").getDefaultMessage();
String expectErrorMessage = "Account does not exist";
assertThat(expectErrorMessage, is(actualErrorMessage)); // Check error message
}

public void executeScript(String file) {
Resource resource = new ClassPathResource(file, getClass());

ResourceDatabasePopulator rdp = new ResourceDatabasePopulator();
rdp.addScript(resource);
rdp.setSqlScriptEncoding("UTF-8");
rdp.setIgnoreFailedDrops(true);
rdp.setContinueOnError(false);

Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
rdp.populate(conn);
}

}

Impression

It is just a unit test of the controller, and we do not consider acquiring the rendering result on the View side for testing (and do not apply login filter etc.). It seems that if you use HtmlUnit, you can get the rendering result, so I’ll give it to Qiita on another occasion.

References

Test data management by Java + Spring (3) How to write tests for [SpringBoot] Controller Spring test official document