Junit 5 & Spring Test을 이용한 TDD 환경 세팅
@ ExtendWith (SpringExtension .class )
@ SpringBootTest (classes = KkApplication .class )
@ ActiveProfiles ("test" )
public abstract class SpringTestSupport {
}
TestContext를 사용하려면 위의 SpringTestSupport를 상속받아 테스트 코드를 개발한다.
@ExtendWith는 Junit4의 RunWith(SpringRunner.class)와 비슷하다고 생각하면 된다.
@SpringBootTest는 @SpringBootApplication이 붙은 애너테이션을 찾아 context를 찾는다.
@SpringBootApplication을 사용하지 않고 다음과 같이 애너테이션을 풀어 개발했다면 위와 같이 classes필드를 통해 지정해야한다.
@ Configuration
@ EnableAutoConfiguration (exclude = { DataSourceAutoConfiguration .class , HibernateJpaAutoConfiguration .class })
@ ComponentScan (
basePackageClasses = KkApplication .class ,
excludeFilters = {
@ ComponentScan .Filter (type = FilterType .CUSTOM , classes = TypeExcludeFilter .class ),
@ ComponentScan .Filter (type = FilterType .CUSTOM , classes = AutoConfigurationExcludeFilter .class )
})
public class KkApplication {
public static void main (String [] args ) {
SpringApplication .run (KkApplication .class , args );
}
}
@ AutoConfigureMockMvc
public abstract class SpringMockMvcTestSupport extends SpringTestSupport {
@ Autowired
protected MockMvc mockMvc ;
}
@AutoConfigureMockMvc를 통해 MockMvc를 Builder없이 주입 받을 수 있다. 스프링부트의 매력인 Auto Config의 장점이다. 이 애너테이션을 상속받아 controller 테스트들을 진행한다. 다음은 샘플 테스트 코드이다.
class HealthCheckerControllerTest extends SpringMockMvcTestSupport {
@ Test
void healthCheckTest () throws Exception {
this .mockMvc .perform (get ("/v1/health-checker" ))
.andDo (print ())
.andExpect (status ().is (HttpStatus .OK .value ()))
.andExpect (content ().contentType (MediaType .APPLICATION_JSON_UTF8_VALUE ))
.andExpect (jsonPath ("$.deployDate" ).exists ())
.andExpect (jsonPath ("$.deployVersion" ).exists ())
.andExpect (jsonPath ("$.distributor" ).exists ());
}
}
public class MockitoExtension implements TestInstancePostProcessor , ParameterResolver {
@ Override
public void postProcessTestInstance (Object testInstance , ExtensionContext context ) {
MockitoAnnotations .initMocks (testInstance );
}
@ Override
public boolean supportsParameter (ParameterContext parameterContext , ExtensionContext extensionContext ) {
return parameterContext .getParameter ().isAnnotationPresent (Mock .class );
}
@ Override
public Object resolveParameter (ParameterContext parameterContext , ExtensionContext extensionContext ) {
return getMock (parameterContext .getParameter (), extensionContext );
}
private Object getMock (Parameter parameter , ExtensionContext extensionContext ) {
Class <?> mockType = parameter .getType ();
Store mocks = extensionContext .getStore (Namespace .create (MockitoExtension .class , mockType ));
String mockName = getMockName (parameter );
if (mockName != null ) {
return mocks .getOrComputeIfAbsent (mockName , key -> mock (mockType , mockName ));
} else {
return mocks .getOrComputeIfAbsent (mockType .getCanonicalName (), key -> mock (mockType ));
}
}
private String getMockName (Parameter parameter ) {
String explicitMockName = parameter .getAnnotation (Mock .class ).name ().trim ();
if (!explicitMockName .isEmpty ()) {
return explicitMockName ;
} else if (parameter .isNamePresent ()) {
return parameter .getName ();
}
return null ;
}
}
위의 MockitoExtension 클래스를 @ExtendWith의 value 필드에 넣음으로써 Mock테스트에 필요한 곳에 애너테이션을 달면 된다. 다음은 샘플 코드이다.
import static org .mockito .Mockito .when ;
@ ExtendWith (MockitoExtension .class )
class HealthCheckerControllerTest extends SpringMockMvcTestSupport {
@ Mock
private BookRepository bookRepository ;
@ InjectMocks
private BookService bookService ;
private givenBookRepository () {
when (bookRepository .findByXXX (..)).thenReturn (...);
}
@ Test
void healthCheckTest () throws Exception {
this .mockMvc .perform (get ("/v1/health-checker" ))
.andDo (print ())
.andExpect (status ().is (HttpStatus .OK .value ()))
.andExpect (content ().contentType (MediaType .APPLICATION_JSON_UTF8_VALUE ))
.andExpect (jsonPath ("$.deployDate" ).exists ())
.andExpect (jsonPath ("$.deployVersion" ).exists ())
.andExpect (jsonPath ("$.distributor" ).exists ());
}
}
여기서 @Test메서드에 @Mock 파라미터로 바로 줄 수도 있다. @Test메서드에 파라미터를 줄 수 있는 것은 Junit4와 달라진 Junit5의 장점인 것 같다.
org.junit.jupiter.api.Test를 import하여 @Test를 이용하여야 @ExtendWith(SpringExtension.class)를 통해 컨텍스트를 끌어 올 수 있다. null pointer exception이 발생한다면 org.junit.test가 import 되어있는지 확인해보자.