Spring Context Caching

Spring TestContext Framework는 Test Suite(테스트 클래스)에 대한 ApplicationContext(or WebApplicationContext)를 로드한 뒤 static cache에 저장함

저장된 컨텍스트는 동일한 Test Suite내에서 동일한 Unique Context Configuration을 사용하는 모든 하위 테스트에서 재사용됨

또한 서로 다른 Test Suite들도 ApplicationContext에 대한 Configuration이 동일하다면 재사용됨

ApplicationContext Unique Identifier

ApplicationContext를 재사용할 수 있는 이유

ApplicationContext를 static cache에 저장할 때 Test Suite의 Configuration Parameter를 조합하여 생성된 고유 키를 사용함

테스트 클래스마다 Configuration이 다르지 않은 경우 동일한 키를 가지므로 캐시된 컨텍스트를 재사용함

Configuration Parameters

@SpringBootTest

springboot의 기능을 더한 spring-test의 @ContextConfiguration 대체 어노테이션

ApplicationContext

WebEnvironment 속성

UseMainMethod 속성

일반적으로 @SpringBootTest가 찾은 Test Configuraiton은 main @SpringBootApplication이 됨

@SpringBootApplication이 선언된 클래스는 main 메서드를 통해 SpringApplication.run()을 호출함

다만 @SpringBootTest는 main 메서드를 호출하지 않고 해당 클래스 자체를 사용하여 ApplicationContext를 생성함

main 메서드를 실행해야 될 경우 속성 값을 변경

TestConfiguration

스프링 부트의 @*Test 어노테이션은 테스트가 포함된 패키지부터 @SpringApplication 또는 @SpringBootConfiguration 어노테이션이 있는 패키지까지 찾음

따라서 스프링의 @ContextConfiguraiton(classes=...) 처럼 @Configuration을 로드하기 위한 설정이 필요하지 않음

별도의 Configuration이 필요한 경우 해당 테스트 클래스에 @TestConfiguration을 선언한 내부 클래스를 생성하여 사용

참고로 @TestConfiguration은 @Component가 포함된 복합 어노테이션이므로 컴포넌트 스캔의 대상이 됨

만약 컴포넌트 스캔 대상에서 제외하고 싶다면 아래처럼 @Import에 명시하면 됨

@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {

	@Test
	void exampleTest() {
		// ...
	}

}

Mocking, Spying Spring Beans

특정 스프링 빈을 mocking, spying할 수 있음

@MockBean : ApplicationContext Bean에 대한 Mockito Mock 정의

@SpyBean : ApplicationContext Bean에 대한 Mockito Spy 정의

참고사항

Spring은 테스트 간에 application context를 캐시하고, 동일한 configuration을 공유한 경우 재사용함

@MockBean과 @SpyBean은 cache key에 영향을 주기 때문에 context의 수를 늘릴 가능성이 높음

Test With @Transactional

@SpringBootTest의 WebEnvironment 속성에 따른 동작 방식 차이

Auto-configured Tests

특정 부분에 대한 테스트(slice test)만 가능하도록 자동 설정해주는 기능

spring-boot-test-autoconfigure 모듈에서 테스트 목적에 따른 어노테이션이 제공됨

각 어노테이션은 @*Test로 끝나며 ApplicationContext를 로드하고, 자동 설정을 위한 @AutoConfigure... 어노테이션이 하나 이상 포함됨

참고사항

Auto-configured Spring MVC Tests

@WebMvcTest

기능

테스트 예시

@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

  @Autowired
  private MockMvc mvc;

  @MockBean
  private UserVehicleService userVehicleService;

  @Test
  void testExample() throws Exception {
    given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
    this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
  }

}

MVC Testing With Spring Security

Spring Security Test 어노테이션 공통사항

SecurityContext 설정 시점 변경

setupBefore 속성 기본 값이 TestExecutionEvent.TEST_METHOD이므로 SecurityContext는 TestExecutionListener.beforeTestMethod 이벤트 중에 설정되는데 JUnit의 @Before 이전에 발생함

JUnit의 @Before 이후(동시에 테스트 메서드 실행 전)로 설정해야 된다면 @WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) 지정

@WithMockUser

@WithMockUser 테스트 예시(기본 값)

@WebMvcTest(UserController.class)
class MySecurityTests {

	@Autowired
	private MockMvc mvc;

	@Test
	@WithMockUser
	void requestProtectedUrlWithUser() throws Exception {
		this.mvc.perform(get("/"));
	}

}

@WithMockUser 테스트 예시(값 지정)

@Test
@WithMockUser(username="spring man", roles={"USER", "ADMIN"})
void requestProtectedUrlWithUser() throws Exception {
  this.mvc.perform(get("/"));
}

@WithMockUser Class에 선언

모든 테스트 메서드마다 지정된 유저를 사용함

JUnit5 @Nested 클래스를 감싼 상위 클래스에 선언하면 해당 테스트 클래스의 모든 하위 테스트 메서드에도 적용됨

@WebMvcTest
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public class WithMockUserTests {

  @Nested
  public class TestSuite1 {
    // ... all test methods use admin user
  }
}

@WithAnonymousUser

Anonymous User로 가정하는 어노테이션

클래스에 @WithMockUser가 선언되어 있는 경우 메서드의 @WithAnonymousUser가 오버라이딩됨

@WebMvcTest
@WithMockUser
public class WithUserClassLevelAuthenticationTests {

	@Test
	public void withMockUser() {
	}

	@Test
	@WithAnonymousUser
	public void anonymous() throws Exception {
		// override default to run as anonymous user
	}
}

@WithUserDetails

커스텀 UserDetailsService(커스텀 타입과 UserDetails를 구현한 객체 반환)을 통해 테스트 유저를 생성할 때 사용하는 어노테이션

아래는 빈으로 등록된 UserDetailsService이 반환하는 user의 username을 prinpal로 갖는 UsernamePasswordAuthentcationToken 타입의 Authentication을 사용함

@Test
@WithUserDetails
public void getMessageWithUserDetails() {
  String message = messageService.getMessage();
	...
}

지정된 username을 지정하여 UserDetailsService에서 조회할 수 있음

@Test
@WithUserDetails("customUsername")
public void getMessageWithUserDetailsCustomUsername() {
	String message = messageService.getMessage();
	...
}

특정 UserDetailsService 빈을 조회하여 지정된 username을 조회할 수 있음

@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
public void getMessageWithUserDetailsServiceBeanName() {
	String message = messageService.getMessage();
	...
}

vs @WithMockUser

@WithSecurityContext

테스트 시 Spring Security Test 대신 직접 SecurityContext를 설정하는 경우 사용하는 어노테이션

  1. @WithSecurityContext를 사용한 커스텀 어노테이션 생성

@WithMockCustomUser는 @WithSecurityContext를 가지고 있는 메타 어노테이션임

@WithSecurityContext의 factory 속성 값에 SecurityCotext를 설정하는 클래스 지정

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {

  String username() default "rob";

  String name() default "Rob Winch";
}
  1. SecurityContext 설정 클래스 구현

@Autowired을 통해 UserDetailsService를 주입받을 수도 있음

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
    
  private UserDetailsService userDetailsService;
  
  @Autowired
  public WithMockCustomUserSecurityContextFactory(UserDetailsService userDetailsService) {
      this.userDetailsService = userDetailsService;
  }
    
  @Override
  public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
  
    CustomUserDetails principal =
            new CustomUserDetails(customUser.name(), customUser.username());
    Authentication auth =
            UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities());
    context.setAuthentication(auth);
    return context;
  }
}

Auto-configured Data JPA Tests

@DataJpaTest

기능

트랜잭션

엔티티 매니저

}


**DB 설정**

`@AutoConfigureTestDatabase` 어노테이션 사용
```java
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {
	// ...
}

Auto-configured REST Clients

@RestClientTest

기능

빈 지정

테스트에 사용되는 빈에서 RestClient.Builder를 사용하거나 rootUri 호출없이 RestTemplateBuilder를 사용하는 경우 전체 URI를 MockRestServiceServer의 expectations에 사용해야 됨

@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientServiceTests {

	@Autowired
	private RemoteVehicleDetailsService service;

	@Autowired
	private MockRestServiceServer server;

	@Test
	void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
		this.server.expect(requestTo("https://example.com/greet/details"))
			.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
		String greeting = this.service.callRestService();
		assertThat(greeting).isEqualTo("hello");
	}

}

참고

spring boot test docs

spring test context caching

testing with spring security