Open KATEKEITH opened 1 year ago
@MockBean
private lateinit var partnerClient: PartnerClient
@Test
fun `register mock bean test`( ) {
// given
given(partnerClient.getPartnerBy(brn))
.willReturn()
// when
// then
}
Application Context를 N 번 초기화하는 문제가 발생한다 !!!
class ShopRegistrationServiceMockBeanTest(
private val shopRegistrationService: ShopRegistrationService
) : TestSupport( ) {
@Bean
private lateinit var partnerClient: PartnerClient
@Test
fun `register mock bean test`( ) {
// given
given(partnerClient.getPartnerBy(brn))
.willReturn()
// when
// then
}
}
@TestConfiguration
class ClientTestConfiguration {
@Bean
@Primary
fun mockPartnerClient( ) = mock(PartnerClient::class.java)
}
테스트에서만 사용하기 위해 @TestConfiguration으로 지정해서 Mock 객체를 등록한다.
class ShopRegistrationServiceMockBeanTest(
private val shopRegistrationService: ShopRegistrationService,
private val mockPartnerClient: PartnerClient
) : TestSupport( ) {
@BeforEach
fun resetMock( ) {
Mockito.restt(mockPartnerClient)
}
@Test
fun `register mock bean test`( ) {
// given
given(partnerClient.getPartnerBy(brn))
.willReturn(PartnerResponse(brn, name))
// when
// then
}
}
class PartnerClient(
private val restTemplate : RestTemplate
) {
fun getPartnerBy(brn: String): PartnerResponse {
return restTemplate
.getForObject(
"",
PartnerResponse::class.java
)!!
}
}
@Service
class PartnerClientService(
private val partnerClient: PartnerClient
) {
fun getPartnerBy(): PartnerResponse {
val response = partnerClient.getPartnerByResponse(brn)
if (response.statusCode.is2xxSuccessful.not()) {
throw IllegalArgumentException("...")
}
return response.body !!
}
}
@Import(ClientTestConfiguration::class)
class PartnerClientServiceTest(
private val partnerClientServuce: PartnerClientService,
private val :
) : TestSupport() {
@Test
fun `getPartnerBy 200`() {
// given
given(partnerClient.getPartnerBy(brn))
.willReturn(
ResponseEntity(
response,
HttpStatus.OK
)
)
// when
val result = partnerClientService.getPartnerBy(brn)
// then
then().isEqualTo()
then().isEqualTo()
}
}
@Import(ClientTestConfiguration::class)
class PartnerClientServiceTest(
private val partnerClientServuce: PartnerClientService,
private val :
) : TestSupport() {
@Test
fun `getPartnerBy 400`() {
// given
given(partnerClient.getPartnerBy(brn))
.willReturn(
ResponseEntity(
response,
HttpStatus.BAD_REQUEST
)
)
// when
thenThrownBy {
partnerClientService.getPartnerBy(brn)
}
.isInstanceOf(Exception::class.java)
// then
}
}
기존 코드는 2xx이 아닌 경우 Exception을 발생시키고 있어 코드 변경 필요
HTTP 통신 책임을 위임 했지만 객체의 요청/ 응답이 외부 라이브버리에 지나치게 의존
@Service
class PartnerClientService(
private val partnerClient: PartnerClient
) {
fun getPartnerBy(): PartnerResponse {
val response = partnerClient.getPartnerByResponse(brn)
if (response.statusCode.is2xxSuccessful.not()) {
throw IllegalArgumentException("...")
}
return response.body !!
}
fun getPartner(brn: String): ResponseEntity<PartnerResponse> {
return partnerClient.getPartnerByResponse(brn)
}
}
@Service
class PartnerClientService(
private val partnerClient: PartnerClient
) {
fun getPartnerBy(): PartnerResponse {
val response = partnerClient.getPartnerByResponse(brn)
if (response.statusCode.is2xxSuccessful.not()) {
throw IllegalArgumentException("...")
}
return response.body !!
}
fun getPartner(brn: String): ResponseEntity<PartnerResponse> {
return partnerClient.getPartnerByResponse(brn)
}
fun getPartner(brn: String): Pair<Int, PartnerResponse?> {
val partnerByResponse = partnerClient.getPartnerByResponse(brn)
return Pair(
first = partnerByResponse.statusCode.value(),
second = partnerByResponse.body
)
}
}
주문은 매우 복잡하고 중요한 Business Logic으로 다양한 케이스의 테스트 코드가 필요하다.
class OrderServiceSupport {
fun order(
product: Product,
orderDate: LocalDate,
): Order {
// 상품 정보 조회 하여 금액 및 상품 재고 확인, 재고가 없는 경우 예외 처리 등등
// 환율 정보 조회 하여 특정 국가 환율로 계산
// 쿠폰 정보 조회하여 적용 가능한 상품인지 확인, 가맹점과 할인, 가맹점과 할인 금액 부담 비율 등등 계산
// 가맹점 정보 조회하여 수수료 정보등 조
return Order()
}
}
fun order(
): String {
val product = producrQueryService.findById(productId)
val exchangeRateResponse = exchangeRateClientImpl.getEachangeRate(orderDate, "USD", "KRW")
val coupon = couponQueryService.findByCode(couponCode)
val shop = shopQueryService.findById(shopId)
// 복잡한 로직은... OrderServiceSupport 객체로 위임
val order = OrderServiceSupport().order(...)
val order = save(order)
return order.ordernumber
}
복잡한 로직의 책임은 위임하고 여러 인프라의 데이터 조회의 책임만 할당한다.
다양한 케이스보단 인프라 조회가 관심사이다.
신규 가맹점 등록 Flow
1차 시도 - HTTP Mock Server Test Code
입력받은 값을 그대로 영속화하는 코드 작성
PartnerClient로 HTTP 통신하여 데이터를 받아와서 영속화 코드 작성