스프링부트 테스트하기

테스트

@Requestmapping(method=RequestMethod.POST)
Public String addToReadingList(Book book) {
	book.setReader(reader);
	readingListRepository.save(book);
	return “redirect:/”;
}

@RequestMapping 애너테이션을 무시하고 보면 이 메서드는 일반적인 자바 메서드다. ReadingListRepository의 목(Mock) 구현체를 제공한 후 addToReadingLisrt() 메서드를 직접 호출하여 반환 값을 검증하고 리포지토리의 save() 메서드 호출을 확인하는 테스트는 만들기가 그리 어렵지 않다.

하지만 이 테스트의 문제는 메서드 자체만 테스트한다는 것이다. 테스트를 아예 안 하는 것보다는 낫지만, /로 들어오는 POST 요청을 처리하는 부분은 테스트할 수 없다. 폼 필드들을 Book 매개변수에 제대로 연결 했는지도 테스트할 수 없다. 게다가 메서드가 반환한 String이 특정 값을 포함하는지 검증할 수는 있어도 메서드 처리를 완료한 후 요청을 /로 리다이렉트 했는지 명확히 테스트할 수는 없다.

웹 애플리케이션을 올바르게 테스트하려면 웹 애플리케이션에 실제 HTTP 요청을 보내고 애플리케이션이 요청을 제대로 처리했는지 검증할 방법이 있어야 한다. 다행히 스프링 부트 애플리케이션 개발자에게는 웹 애플리케이션을 테스트할 수 있는 두 가지 옵션이 있다.

스프링 부트 애플리케이션 테스트 옵션

  • 스프링 Mock MVC : 애플리케이션 서버를 구동하지 않고도 서블릿 컨테이너와 거의 비슷하게 작동하는 목 구현체로 컨트롤러를 테스트할 수 있다.

  • 웹 통합 테스트 : 톰캣, 제티 등 내장 서블릿 컨테이너에서 애플리케이션을 실행하여 실제 애플리케이션 서버에서 애플리케이션을 테스트할 수 있다.

Mock MVC를 설정하려면 MockMvcBuilders를 사용한다. 이 클래스는 정적 메서드 두 개를 제공한다.

  • standaloneSetup() : 수동으로 생성하고 구성한 컨트롤러 한 개 이상을 서비스할 Mock MVC를 만든다.

  • webAppContextSetup() : 구성된 컨트롤러 한 개 이상을 포함하는 스프링 애플리케이션 컨텍스트를 사용하여 Mock MVC를 만든다.

이 두 옵션의 가장 큰 차이는 standaloneSetup() 메서드는 테스트할 컨트롤러를 수동으로 초기화하고 주입하기를 기대하는 반면, webAppContextSetup() 메서드는 (스프링이 로드한) WebApplicationContext의 인스턴스로 작동한다는 점이다.standaloneSetup() 메서드는 한 컨트롤러에 집중하여 테스트하는 용도로만 사용한다는 점에서 유닛 테스트와 유사하다. 반면에 webAppContextSetup() 메서드는 스프링이 컨트롤하는 물론 의존성까지 로드하여 완전한 통합 테스트를 할 수 있게 한다.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=ReadingListApplication.class)
@WebAppConfiguration
Public class MockvcWebTests {
	@Autowired
	private WebApplicationContext webContext;		// WebApplicationConext 주입
	
	private MockMvc mockMvc;
	
	@Before
	public void setupMockMvc() {
		mockMvc = MockMvcBuilders
							.webAppContextSetup(webContext)
							.build();
	}
}
@Test
Public void postBook() throws Exception {
	mockMvc.perform(post(“/”)		// POST 요청 수행
		.contentType(MediaType.APPLICATION_FORM_URLENCODED)
		.param(“title”, “BOOK TITLE”)
		.param(“author”,”BOOK AUTHOR”)
		.param(“isbn”,123456789)
		.param(“description”,”DESCRIPTION”)
		.andExpect(status().is3xxRedirection())
		.andExpect(header().string(“Location”, “/”));

	Book expectedBook = new Book();
	expectedBook.setId(1L);
	expectedBook.setReader(“craig”);
	expectedBook.setTitle(“BOOK TITLE”);
	expectedBook.setAuthor(“BOOK AUTHOR”);
	expectedBook.setIsbn(“123456789”);
	expectedBook.setDescription(“DESCRIPTION”);

	mockMvc.perform(get(“/”))
		.andExpect(status().isOK())
		.andExpect(view().name(“readingList”))
		.andExpect(model().attributeExists(“books”))
		.andExpect(model().attribute(“books”, hasSize(1)))
		.andExpect(model().attribute(“books”, contains(samePropertyValuesAs(expectedBook))));

}

웹 보안 테스트

테스트하기 이전에 dependency를 추가해야 한다. ( Spring-security-test )

springSecurity() 메서드는 Mock MVC용으로 스프링 시큐리티를 활성화하는 Mock MVC 구성자를 반환한다.

그렇다면 어떻게 인증된 요청을 수행할 수 있을까?

  • @WithMockUser : 지정한 사용자 이름, 패스워드, 권한으로 UserDetails를 생성한 후 보안 컨텍스트를 로드한다.

  • @WithUserDetails : 지정한 사용자 이름으로 UserDetails 객체를 조회하여 보안 컨텍스트를 로드한다.

@Test
@WithMockUser(username=“craig”, password=“password”, roles=“READER”)
Public void homepage_authenticatedUser() throws Exception {

}
@Test
@WithUserDetails(“craig”)
Public void homepage_authenticatedUser() throws Exception {
	Reader expectedReader = new Reader();
	expectedReader.setUsername(“craig”);
	expectedReader.setPassword(“password”);
	expectedReader.setFullname(“Craig Walls”);

	mockMvc.perform(get(“/”))
		.andExpect(status().isOK())
		.andExpect(view().name(“readingList”))
		.andExpect(model().attribute(“reader”, samePropertyValuesAs(expectedReader)))
		.andExpect(model().attribute(“books”, hasSize(0)))
		.andExpect(model().attribute(“amazonID”, “habuma-20”));
}
@Override
Protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	auth.userDetailsService(userDetailsService());
}

------

@Bean
Public UserDetailService userDetailsService() {
	return new UserDetailsService() {
		@Override
		public UserDetails loadUserByUsername(String username) {
			return readerRepository.findOne(username);
		}
	}
}

Last updated

Was this helpful?