From c5e5a3047b9d256c38ab97137c6bcf702e668e4d Mon Sep 17 00:00:00 2001 From: Colt Date: Tue, 26 Apr 2022 20:57:48 +0900 Subject: [PATCH] =?UTF-8?q?[=EB=A7=8C=EB=93=A4=EB=A9=B4=EC=84=9C=20?= =?UTF-8?q?=EB=B0=B0=EC=9A=B0=EB=8A=94=20=ED=81=B4=EB=A6=B0=20=EC=95=84?= =?UTF-8?q?=ED=82=A4=ED=85=8D=EC=B2=98]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=98=88=EC=A0=9C=20=EC=BD=94=EB=93=9C=20=EB=B3=B4=EC=B6=A9=20?= =?UTF-8?q?(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 만들면서 배우는 클린 아키텍처 initial commit * refactor : 프로젝트 진입점 클래스 이름 변경 * docs : README.md 헥사고날 아키텍처 항목 추가 * docs(README.md) : 내용 정리 추가 * feat(user.domain) : 도메인 모델 User 추가 * feat(user.domain) : User 의 nickname 프로퍼티 값 객체로 포장 * refactor(User) : 닉네임 변경 함수 이름 수정 * test(user.domain) : 회원 닉네임 변경 테스트 추가 * chore : DB 설정 추가 * feat(user.adapter) : User Entity 구현 * feat : User 닉네임 변경 기능 추가 * refactor(user) : domain 패키지 내부 패키지 구성 추가 및 Entity, Model 이관 * refactor : 사용하지 않는 파일 삭제 * refactor : User 닉네임 변경 기능 컴포넌트 이름 변경 * refactor : User 닉네임 변경 기능 in port 이름 변경 * feat : User Upsert Port 및 Adapter 구현, Service 로직에 추가 * chore : Hexagonal Architecture Process 이미지 추가 * docs(README.md) : 요구사항, 구현 항목 추가 * refactor : 패키지 구성 변경 * feat(user.adapter) : UserMapper 추가 및 적용 * docs(README.md) : 참고자료 및 구현 항목 내용 추가 * refactor : ChangeNicknameRequest, ChangeNicknameResponse 패키지 변경 * refactor : adapter 계층만 application 계층에 의존하도록 통신 객체 추가 및 적용 * refactor : UserEntity @Table 이름 적용 * docs(README.md) : 구현 항목 내용 추가 * refactor : Nickname 입력 유효성 검사 ChangeNicknameRequest 에서 수행하도록 변경 * refactor(user.pojo) : 불필요한 테스트 삭제 * refactor(UserTest) : 오탈자 수정 * build : Kotlin 테스트 라이브러리 추가 * test(user.application) : 닉네임 변경 테스트 추가 * test(user.adapter) : 회원 조회 테스트 추가 * refactor : 불필요한 파일 삭제 * test(user.adapter) : 회원 상태 저장 또는 수정 테스트 추가 * test(user.adapter) : User POJO <-> User Entity 매핑 테스트 추가 * test(user.adapter) : 닉네임 변경 Web Adapter 테스트 추가 * refactor : 불필요한 테스트 파일 삭제 * refactor(user) : 닉네임 변경 테스트 케이스 출력 이름 변경 --- .../build.gradle.kts | 4 ++ .../LearnWithMakingCleanArchitectureApplication.kt | 11 --- .../adapter/in/web/ChangeNicknameWebAdapterTest.kt | 72 +++++++++++++++++++ .../out/persistence/LoadUserPersistenceAdapterTest.kt | 43 +++++++++++ .../persistence/UpsertUserPersistenceAdapterTest.kt | 39 ++++++++++ .../user/adapter/out/persistence/UserMapperTest.kt | 41 +++++++++++ .../application/service/ChangeNicknameServiceTest.kt | 46 ++++++++++++ .../banjjoknim/cleanarchitecture/user/pojo/UserTest.kt | 3 +- ...LearnWithMakingCleanArchitectureApplicationTests.kt | 13 ---- 9 files changed, 247 insertions(+), 25 deletions(-) delete mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/main/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplication.kt create mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/in/web/ChangeNicknameWebAdapterTest.kt create mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/LoadUserPersistenceAdapterTest.kt create mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UpsertUserPersistenceAdapterTest.kt create mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UserMapperTest.kt create mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/application/service/ChangeNicknameServiceTest.kt delete mode 100644 놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplicationTests.kt diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/build.gradle.kts b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/build.gradle.kts index 01fd5a6..8ae5a02 100644 --- a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/build.gradle.kts +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/build.gradle.kts @@ -25,11 +25,15 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + runtimeOnly("com.h2database:h2") runtimeOnly("mysql:mysql-connector-java") runtimeOnly("org.mariadb.jdbc:mariadb-java-client") + testImplementation("org.springframework.boot:spring-boot-starter-test") // testImplementation("org.springframework.security:spring-security-test") + testImplementation("io.mockk:mockk:1.12.3") + testImplementation("com.ninja-squad:springmockk:3.1.1") } tasks.withType { diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/main/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplication.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/main/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplication.kt deleted file mode 100644 index d9a8be3..0000000 --- a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/main/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplication.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.banjjoknim.playground - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication - -@SpringBootApplication -class LearnWithMakingCleanArchitectureApplication - -fun main(args: Array) { - runApplication(*args) -} diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/in/web/ChangeNicknameWebAdapterTest.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/in/web/ChangeNicknameWebAdapterTest.kt new file mode 100644 index 0000000..79861c1 --- /dev/null +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/in/web/ChangeNicknameWebAdapterTest.kt @@ -0,0 +1,72 @@ +package com.banjjoknim.cleanarchitecture.user.adapter.`in`.web + +import com.banjjoknim.cleanarchitecture.user.application.port.`in`.ChangeNicknameResponseData +import com.banjjoknim.cleanarchitecture.user.application.port.`in`.ChangeNicknameUseCase +import com.fasterxml.jackson.databind.ObjectMapper +import com.ninjasquad.springmockk.MockkBean +import io.mockk.every +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.post +import org.springframework.test.web.servlet.result.MockMvcResultHandlers +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.web.context.WebApplicationContext +import org.springframework.web.filter.CharacterEncodingFilter + +@WebMvcTest(ChangeNicknameWebAdapter::class) +class ChangeNicknameWebAdapterTest { + + @Autowired + private lateinit var mockmvc: MockMvc + + @Autowired + private lateinit var objectMapper: ObjectMapper + + @MockkBean + private lateinit var changeNicknameUseCase: ChangeNicknameUseCase + + @BeforeEach + fun setUp(webApplicationContext: WebApplicationContext) { + mockmvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .addFilter(CharacterEncodingFilter("UTF-8")) + .alwaysDo(MockMvcResultHandlers.print()) + .build() + } + + @DisplayName("닉네임 변경 테스트 케이스") + @Nested + inner class ChangeNicknameTestCases { + @Test + fun `닉네임을 변경한다`() { + every { changeNicknameUseCase.changeNickname(any()) } returns ChangeNicknameResponseData(1L) + val request = ChangeNicknameRequest(1L, "banjjoknim") + + mockmvc.post("/users") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + }.andExpect { + content { json("""{"userId":1}""") } + status { isOk() } + } + } + + @Test + fun `잘못된 닉네임 변경 요청에 BadRequest 응답을 반환한다`() { + val request = ChangeNicknameRequest(1L, "banjjoknim!!") + + mockmvc.post("/users") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + }.andExpect { + status { isBadRequest() } + } + } + } +} diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/LoadUserPersistenceAdapterTest.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/LoadUserPersistenceAdapterTest.kt new file mode 100644 index 0000000..8ad8d1d --- /dev/null +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/LoadUserPersistenceAdapterTest.kt @@ -0,0 +1,43 @@ +package com.banjjoknim.cleanarchitecture.user.adapter.out.persistence + +import com.banjjoknim.cleanarchitecture.user.pojo.Nickname +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.context.annotation.Import +import org.springframework.test.context.jdbc.Sql + +@DataJpaTest +@Import(value = [UserMapper::class, LoadUserPersistenceAdapter::class]) +class LoadUserPersistenceAdapterTest { + + @Autowired + private lateinit var loadUserPersistenceAdapter: LoadUserPersistenceAdapter + + @DisplayName("회원 조회 테스트 케이스") + @Nested + inner class LoadUserTestCases { + @Sql(statements = ["INSERT INTO USER VALUES (1, 'old')"]) + @Test + fun `회원이 존재하지 않을 경우 예외가 발생한다`() { + assertThrows("존재하지 않는 회원입니다. userId: 100") { + loadUserPersistenceAdapter.loadUser( + 100L + ) + } + } + + @Sql(statements = ["INSERT INTO USER VALUES (1, 'old')"]) + @Test + fun `회원이 존재할 경우 회원을 조회할 수 있다`() { + val user = loadUserPersistenceAdapter.loadUser(1L) + + Assertions.assertThat(user.id).isEqualTo(1) + Assertions.assertThat(user.nickname).isEqualTo(Nickname("old")) + } + } +} diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UpsertUserPersistenceAdapterTest.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UpsertUserPersistenceAdapterTest.kt new file mode 100644 index 0000000..4c9cd66 --- /dev/null +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UpsertUserPersistenceAdapterTest.kt @@ -0,0 +1,39 @@ +package com.banjjoknim.cleanarchitecture.user.adapter.out.persistence + +import com.banjjoknim.cleanarchitecture.user.pojo.Nickname +import com.banjjoknim.cleanarchitecture.user.pojo.User +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest +import org.springframework.context.annotation.Import +import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.context.jdbc.Sql + +@DataJpaTest +@Import(value = [UserMapper::class, UpsertUserPersistenceAdapter::class]) +class UpsertUserPersistenceAdapterTest { + + @Autowired + private lateinit var upsertUserPersistenceAdapter: UpsertUserPersistenceAdapter + + @Autowired + private lateinit var userEntityRepository: UserEntityRepository + + @DisplayName("회원 상태 저장 및 수정 테스트 케이스") + @Nested + inner class UpsertUserTestCases { + @Sql(statements = ["INSERT INTO USER VALUES (1, 'old')"]) + @Test + fun `회원 상태를 저장하거나 수정한다`() { + val updatedUser = User(1L, Nickname("new")) + + upsertUserPersistenceAdapter.upsertUser(updatedUser) + + val userEntity = userEntityRepository.findByIdOrNull(1L) + assertThat(userEntity?.nickname).isEqualTo(NicknameColumn("new")) + } + } +} diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UserMapperTest.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UserMapperTest.kt new file mode 100644 index 0000000..d4c848e --- /dev/null +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/adapter/out/persistence/UserMapperTest.kt @@ -0,0 +1,41 @@ +package com.banjjoknim.cleanarchitecture.user.adapter.out.persistence + +import com.banjjoknim.cleanarchitecture.user.pojo.Nickname +import com.banjjoknim.cleanarchitecture.user.pojo.User +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest(classes = [UserMapper::class]) +class UserMapperTest { + + @Autowired + private lateinit var userMapper: UserMapper + + @DisplayName("User POJO <-> User Entity 매핑 테스트 케이스") + @Nested + inner class UserMapperTestCases { + @Test + fun `User POJO 를 User Entity 로 변환한다`() { + val user = User(1L, Nickname("banjjoknim")) + + val userEntity = userMapper.mapToDomainEntity(user) + + assertThat(userEntity.id).isEqualTo(1) + assertThat(userEntity.nickname).isEqualTo(NicknameColumn("banjjoknim")) + } + + @Test + fun `User Entity 를 User POJO 로 변환한다`() { + val userEntity = UserEntity(1L, NicknameColumn("banjjoknim")) + + val user = userMapper.mapToDomainModel(userEntity) + + assertThat(user.id).isEqualTo(1) + assertThat(user.nickname).isEqualTo(Nickname("banjjoknim")) + } + } +} diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/application/service/ChangeNicknameServiceTest.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/application/service/ChangeNicknameServiceTest.kt new file mode 100644 index 0000000..e8d7f49 --- /dev/null +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/application/service/ChangeNicknameServiceTest.kt @@ -0,0 +1,46 @@ +package com.banjjoknim.cleanarchitecture.user.application.service + +import com.banjjoknim.cleanarchitecture.user.application.port.`in`.ChangeNicknameRequestData +import com.banjjoknim.cleanarchitecture.user.application.port.out.LoadUserPersistencePort +import com.banjjoknim.cleanarchitecture.user.application.port.out.UpsertUserPersistencePort +import com.banjjoknim.cleanarchitecture.user.pojo.Nickname +import com.banjjoknim.cleanarchitecture.user.pojo.User +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class ChangeNicknameServiceTest { + + private val loadUserPersistencePort = mockk() + private val upsertUserPersistencePort = mockk() + private val changeNicknameService = ChangeNicknameService(loadUserPersistencePort, upsertUserPersistencePort) + + @DisplayName("닉네임 변경 테스트 케이스") + @Nested + inner class ChangeNicknameTestCases { + + private lateinit var testUser: User + private val testChangeNicknameRequestData = ChangeNicknameRequestData(1L, "new") + + @BeforeEach + fun setUp() { + testUser = User(1L, Nickname("old")) + } + + @Test + fun `닉네임을 변경한다`() { + every { loadUserPersistencePort.loadUser(any()) } returns testUser + every { upsertUserPersistencePort.upsertUser(any()) } just Runs + + changeNicknameService.changeNickname(testChangeNicknameRequestData) + + assertThat(testUser.nickname).isEqualTo(Nickname("new")) + } + } +} diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/pojo/UserTest.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/pojo/UserTest.kt index 64d96e2..88421f6 100644 --- a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/pojo/UserTest.kt +++ b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/cleanarchitecture/user/pojo/UserTest.kt @@ -7,7 +7,8 @@ import org.junit.jupiter.api.Test class UserTest { - @DisplayName("회원 이름 변경 테스트 케이스") + + @DisplayName("회원 닉네임 변경 테스트 케이스") @Nested inner class ChangeNicknameTestCases { @Test diff --git a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplicationTests.kt b/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplicationTests.kt deleted file mode 100644 index 46da497..0000000 --- a/놀이터(예제 코드 작성)/learn-with-making-clean-architecture/src/test/kotlin/com/banjjoknim/playground/LearnWithMakingCleanArchitectureApplicationTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.banjjoknim.playground - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class LearnWithMakingCleanArchitectureApplicationTests { - - @Test - fun contextLoads() { - } - -}