본문 바로가기
서버/Kotlin-Spring_Boot

Kotlin-Spring_Boot 강의 정리) 9. PATCH Endpoint

by HDobby 2023. 2. 2.

https://youtu.be/WZVeyLxu_dE

이번엔 테스트를 먼저 생성해 봅시다.

BankControllerTest로 갑시다.

@Nested
	@DisplayName("PATCH /api/banks")
	@TestInstance(TestInstance.Lifecycle.PER_CLASS)
	inner class PatchExistingBank {
		@Test
		fun `should update an existing bank`() {
			// given
			val updatedBank = Bank("1234", 1.0, 1)

			// when
			val performPatchRequest = mockMvc.patch(baseUrl) {
				contentType = MediaType.APPLICATION_JSON
				content = objectMapper.writeValueAsString(updatedBank)
			}

			// then
			performPatchRequest
				.andDo { print() }
				.andExpect {
					status { isOk() }
				}

		}

	}

여태 해왔던 방식과 동일합니다.

코드 200이 아닌 405가 들어와서 실패했습니다.

405는 Method Not Allowed로 이 메서드를 지원하지 않아 발생하게 됩니다.

우리의 경우는 Patch 맵핑이 되어있지 않아 발생하게 됩니다.

@PatchMapping
	fun updateBank(@RequestBody bank: Bank): Bank = bank //TODO

BankController에 해당 맵핑을 추가해 준 뒤 다시 실행해 봅시다.

이번엔 맵핑이 되어있어 통과하게 됩니다.

다시 테스트로 가서 들어온 정보와 입력된 정보가 일치하는지 비교해 봅시다.

performPost.andDo { print() }
				.andExpect {
					status { isCreated() }
					content { contentType(MediaType.APPLICATION_JSON) }
					jsonPath("$.accountNumber") { value("acc123") }
					jsonPath("$.trust") { value("31.415") }
					jsonPath("$.transactionFee") { value("2") }
				}

테스트 코드를 jsonPath로 일일이 검사하는 방법도 있지만,

performPatchRequest
				.andDo { print() }
				.andExpect {
					status { isOk() }
					content {
						contentType(MediaType.APPLICATION_JSON)
						json(objectMapper.writeValueAsString(updatedBank))
					}
				}

위와 같이 content 안에 넣어 한번에 비교할 수도 있습니다.

저렇게 여러 과정을 나눠서 할 필요 없이 짧게도 가능합니다.

mockMvc.get("$baseUrl/${updatedBank.accountNumber}")
				.andExpect { content { { json(objectMapper.writeValueAsString(updatedBank)) } } }

get을 이용해 해당 bank 정보를 가져온 다음 입력한 정보와 일치하는지 비교하는 구문입니다.

@Test
		fun `should add the new bank`() {
			// given
			val newBank = Bank("acc123", 31.415, 2)

			// when
			val performPost = mockMvc.post(baseUrl) {
				contentType = MediaType.APPLICATION_JSON
				content = objectMapper.writeValueAsString(newBank)
			}

			// then
			performPost.andDo { print() }
				.andExpect {
					status { isCreated() }
					content {
						contentType(MediaType.APPLICATION_JSON)
						json(objectMapper.writeValueAsString(newBank))
					}
				}

			mockMvc.get("$baseUrl/${newBank.accountNumber}")
				.andExpect { content { json(objectMapper.writeValueAsString(newBank)) } }
		}

should add the new bank(Bank 추가 테스트) 또한 변경해 주도록 합시다.

작동되는 걸 확인했으니 BankController로 가서 updateBank가 실제로 작동하도록 변경해 줍시다.

 

BankController

package com.study.hello_world.controller

import com.study.hello_world.model.Bank
import com.study.hello_world.service.BankService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/banks")
class BankController(private val service: BankService) {

	@ExceptionHandler(NoSuchElementException::class)
	fun handleNotFound(e: NoSuchElementException): ResponseEntity<String> =
		ResponseEntity(e.message, HttpStatus.NOT_FOUND)

	@ExceptionHandler(IllegalArgumentException::class)
	fun handleNotFound(e: IllegalArgumentException): ResponseEntity<String> =
		ResponseEntity(e.message, HttpStatus.BAD_REQUEST)

	@GetMapping
	fun helloWorld(): Collection<Bank> = service.getBanks()

	@GetMapping("/{accountNumber}")
	fun getBBank(@PathVariable accountNumber: String) = service.getBank(accountNumber)

	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	fun addBank(@RequestBody bank: Bank): Bank = service.addBank(bank)

	@PatchMapping
	fun updateBank(@RequestBody bank: Bank): Bank = service.updateBank(bank)
}

BankService

package com.study.hello_world.service

import com.study.hello_world.datasource.BankDataSource
import com.study.hello_world.model.Bank
import org.springframework.stereotype.Service

@Service
class BankService(private val dataSource: BankDataSource) {
	fun getBanks(): Collection<Bank> {
		return dataSource.retrieveBanks()
	}

	fun getBank(accountNumber: String): Bank = dataSource.retrieveBank(accountNumber)

	fun addBank(bank: Bank): Bank = dataSource.createBank(bank)

	fun updateBank(bank: Bank): Bank = dataSource.updateBank(bank)
}

BankDataSource

package com.study.hello_world.datasource

import com.study.hello_world.model.Bank

interface BankDataSource {

	fun retrieveBanks(): Collection<Bank>

	fun retrieveBank(accountNumber: String): Bank

	fun createBank(bank: Bank): Bank

	fun updateBank(bank: Bank): Bank
}

MockBankDataSource

package com.study.hello_world.datasource.mock

import com.study.hello_world.datasource.BankDataSource
import com.study.hello_world.model.Bank
import org.springframework.stereotype.Repository

@Repository
class MockBankDataSource : BankDataSource {
	val banks = mutableListOf(
		Bank("1234", 3.14, 18),
		Bank("110", 17.0, 0),
		Bank("5678", 0.0, 100),
	)

	override fun retrieveBanks(): Collection<Bank> = banks

	override fun retrieveBank(accountNumber: String): Bank =
		banks.firstOrNull() { it.accountNumber == accountNumber }
			?: throw NoSuchElementException("Could not find a bank with account number $accountNumber aaa")

	override fun createBank(bank: Bank): Bank {
		if (banks.any { it.accountNumber == bank.accountNumber }) {
			throw IllegalArgumentException("Bank with account number ${bank.accountNumber} already exist")
		}
		banks.add(bank)

		return bank
	}

	override fun updateBank(bank: Bank): Bank {
		val currentBank = banks.firstOrNull { it.accountNumber == bank.accountNumber }
			?: throw NoSuchElementException("Could not find a bank with account number $bank.accountNumber aaa")

		banks.remove(currentBank)
		banks.add(bank)

		return bank
	}
}

이번엔 잘못된 요청이 들어왔을 때를 테스트해봅시다.

@Test
		fun `should return BAD REQUEST if no bank with given account number exists`() {
			// given
			val invalidBank = Bank("does_not_exist", 1.0, 1)

			// when
			val performPatchRequest = mockMvc.patch(baseUrl) {
				contentType = MediaType.APPLICATION_JSON
				content = objectMapper.writeValueAsString(invalidBank)
			}

			// then
			performPatchRequest
				.andDo { print() }
				.andExpect { status { isBadRequest() } }

		}

patchExistingBank 클래스 안에 해당 테스트를 추가해 줍니다.

400이 아닌 404가 들어왔다고 합니다.

이전에 NoSuchElementException을 isNotFound()로 예외처리 한 적이 있으므로 테스트를 변경해 줍시다.

 

이전에 진행했던 예외처리

ExceptionHandler(NoSuchElementException::class)
	fun handleNotFound(e: NoSuchElementException): ResponseEntity<String> =
		ResponseEntity(e.message, HttpStatus.NOT_FOUND)

옳지 않은 accountNumber Patch

@Test
		fun `should return BAD REQUEST if no bank with given account number exists`() {
			// given
			val invalidBank = Bank("does_not_exist", 1.0, 1)

			// when
			val performPatchRequest = mockMvc.patch(baseUrl) {
				contentType = MediaType.APPLICATION_JSON
				content = objectMapper.writeValueAsString(invalidBank)
			}

			// then
			performPatchRequest
				.andDo { print() }
				.andExpect { status { isNotFound() } }

		}

 

728x90

댓글