Test/Testing Spring Boot App

Testing Spring Boot App

PHM 2023. 10. 27. 17:10

Repository Layer

@DataJpaTest

- 저장소 계층만 테스트하기  위해 사용

- 내장된 메모리에 자동으로 구성

- Spring Data JPA 리포지토리 및 JPA 관련 테스트할 수 있다.


Service Layer

@ExtendWith(MockitoExtension.class)

- JUnit 5와 Mockito를 통합하여 테스트를 작성하고 실행할 수 있도록 도와준다.

 

@Mock

- 테스트 대상 클래스에서 다른 클래스 또는 의존성 객체를 대신하는 모의 객체를 생성한다.

- 즉, 테스트 대상 클래스 내에서 특정 메서드를 호출하거나 특정 객체를 사용할 때, 이를 실제 객체 대신 모의 객체가 대신 동작한다.

 

@InjectMocks

- 테스트 대상 클래스에 @Mock 으로 생성된 모의 객체를 주입한다.

- 즉, @InjectMocks를 붙인 클래스 내부에서 @Mock로 생성된 모의 객체들을 사용하며 테스트 대상 클래스의 행위를 검증한다.

 

BDDMockito.given

- Mock 객체에게 메서드 호출에 대한 기대값을 정의할 수 있다.

- 이렇게 정의된 기대값에 따라 Mock 객체가 메서드를 호출하면 특정한 반환값을 제공하거나 예외를 던질 수 있다. 이를 통해 단위 테스트 중에 외부 의존성을 컨트롤하고 특정 시나리오를 시뮬레이션할 수 있다.
- void를 반환하는 메서드를 스텁할 때 : willDoNothing() 사용

willDoNothing().given(employeeRepository).deleteById(1L);

 

BDDMockito.verify

verify(employeeRepository, never()).save(any(Employee.class));

- employeeRepository의 save 메서드가 호출되지 않았고, save 메서드가 어떤 종류의 Employee 객체를 인수로 받더라도 무시하겠다.

- 즉, save 메서드 호출 여부를 확인하면서 인수에 관한 구체적인 내용을 무시하고 검증하는 방법


Controller Layer

@WebMvcTest

- 스프링 부트는 스프링 컨트롤러를 테스트하기 위해 해당 어노테이션을 제공

- 기본적으로 애플리케이션 컨텍스트를 지우고 필요한 빈만 로드한다.

- 필요한 구성요소만 로드하므로 JUnit 테스크 케이스가 훨씬 빨라진다.

 

@SpringBootTest

- 통합테스트, 컨트롤 레이어에서 데이터베이스까의 전체 흐름을 테스트할 때 사용

- 전체 애플리케이션 컨텍스트를 로드한다.

 

예시)

@WebMvcTest
class EmployeeControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private EmployeeService employeeService;

    @Test
    @DisplayName("컨트롤러 테스트")
    void givenEmployeeObject_whenCreateEmployee_thenReturnSavedEmployee() throws Exception {
        // given
        Employee employee = Employee.builder()
                .firstName("p")
                .lastName("hm")
                .email("phm@naver.com")
                .build();
        BDDMockito.given(employeeService.saveEmployee(ArgumentMatchers.any(Employee.class)))
                .willAnswer((invocation) -> invocation.getArgument(0));

        // when
        ResultActions response = mockMvc.perform(MockMvcRequestBuilders.post("/api/employees")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(employee)));

        // then
        response.andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andExpect(MockMvcResultMatchers.jsonPath("$.firstName", CoreMatchers.is(employee.getFirstName())))
                .andExpect(MockMvcResultMatchers.jsonPath("$.lastName", CoreMatchers.is(employee.getLastName())))
                .andExpect(MockMvcResultMatchers.jsonPath("$.email", CoreMatchers.is(employee.getEmail())));

    }
}

- given 부분 : saveEmployee가 호출 될 때 전달된 Employee 객체를 반환하기 위함.

- MockMvcRequestBuilders / MockMvcResultHandlers / MockMvcResultMatchers / CoreMatchers

- $ 는 JSON 개체의 배열을 의미한다.


Integratioin Testing

@SpringBootTest

- 전체 애플리케이션 컨텍스트의 로드, 모든 빈을 로드

- 내장 서버를 시작하고 모든 항목을 로드한다.

 

Repository Integration

@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

- 레파지토리 단위 테스트는 H2 인메모리 데이터베이스를 쓴다. 해당 설정을 추가하면 H2를 비활성화되고 기본적으로 연결된 데이터베이스를 사용한다.

 


Integration Testing using Testcontainers

- Test Containers 는 경량성을 제공하는 JUnit 테스트를 지원하는 Java 라이브러리이다.

- 기본적으로 테스트 컨테이너를 사용하면 JUnit 테스트 케이스 자체에서 Docker 컨테이너를 사용할 수 있다.

- 즉, 테스트 컨테이너를 사용하면 외부 컨테이너를 설치할 필요가 없다는 것을 의미한다.

 

@Testcontainers

- JUnit5와 Testcontainers의 통합을 제공한다는 것을 의미

- @Container 를 선택하고 컨테이너 수명 주기 메서드를 호출

 

@Container

@Container
private static final MySQLContainer MY_SQL_CONTAINER = new MySQLContainer();

- MySQLContainer 클래스는 기본적으로 Docker 허브에서 Docker 이미지를 다운로드한다.

- 컨테이너 인스턴스를 생성할 때마다 정적으로 정의해야 테스트 메서드마다 사용가능!

 

싱글톤 컨테이너 패턴

public abstract class AbstractionBaseTest {

    static final MySQLContainer MY_SQL_CONTAINER;

    static {
        MY_SQL_CONTAINER = new MySQLContainer("mysql:lastest")
                .withUsername("username")
                .withPassword("password") 
                .withDatabaseName("ems");

        MY_SQL_CONTAINER.start();
    }

    @DynamicPropertySource  // 애플리케이션 컨텍스트에 등록
    public static void dynamicPropertySource(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", MY_SQL_CONTAINER::getJdbcUrl);
        registry.add("spring.datasource.username", MY_SQL_CONTAINER::getUsername);
        registry.add("spring.datasource.password", MY_SQL_CONTAINER::getPassword);
    }
}

- @Testcontainers / @Container 삭제 가능

- 해당 MySQLContainer 가 쓰이는 곳에서 상속