🔥 TIL-Day 70 KotlinとSpringboot 01 CRUD Rest API実装とJunitテスト
完全なソースGithub
これは、コードをそのまま書き込むのではなく、初めて接触した言語であるため、エラーかもしれません.
タイトル、コンテンツのArticleクラスを定義します.
kotlinの基本構文を理解するためにDBではなくPojoでクラスを記述します.
以上のカテゴリは、
body(中括弧)がないのが印象的でした
次に、classの前のデータキーワードはEquals And HashCodeおよびToStringを提供する.Lombokの@Dataと似ています.これも便利ですが、上のクラスがエンティティである場合は注意が必要です.
メモリ(Map)を使用してレポートを実装します.
これはKotlinの長所の一つnull safetyの一部であり、JavaのOptionに似た概念であればよい.
以降はテスト部分で発生するが、
色
文法的には本当によかったです.
これは、コードをそのまま書き込むのではなく、初めて接触した言語であるため、エラーかもしれません.
📌 ドメイン設計
タイトル、コンテンツのArticleクラスを定義します.
kotlinの基本構文を理解するためにDBではなくPojoでクラスを記述します.
data class Article(
val title: String,
val content: String,
)
Kotlinの最初の魅力点...以上のカテゴリは、
생성자
、Getter
、Equals And HashCode
、ToString
を含む.body(中括弧)がないのが印象的でした
primary constructor
のフィールドタイプがval
であることを指定した場合、このフィールドpublic
を読み取ることができる.実際、Javaクラスファイルにコンパイルした結果もGetterを呼び出します.var
と記載されている場合は、このフィールドをpublic
に完全に開くことに相当します.読み取りと変更は、フィールドに直接アクセスできます.(実際のJavaコード呼び出しgetter、setter)primary constructorでvarを使うのは少し危険かもしれませんか? エンティティの変更には、ビジネスロジックを反映する方法が望ましい。
次に、classの前のデータキーワードはEquals And HashCodeおよびToStringを提供する.Lombokの@Dataと似ています.これも便利ですが、上のクラスがエンティティである場合は注意が必要です.
📌 メモリレポートの実装
メモリ(Map)を使用してレポートを実装します.
@Repository
class ArticleRepositoryImpl : ArticleRepository {
var sequence: Long = 0L
var store = mutableMapOf<Long, Article>(
++sequence to Article("article1", "content1"),
++sequence to Article("article2", "content2"),
++sequence to Article("article3", "content3"),
)
}
Mapについては,可変Mapを用いて修正を行った.CRUDの実装
getArticle
のリターンタイプはArticle?
です.これはKotlinの長所の一つnull safetyの一部であり、JavaのOptionに似た概念であればよい.
Javaの場合、nullオブジェクトに対してlengthやgetterなどのメソッドを呼び出すと、NullPointException(NPE)が生成されます。これを防ぐ方法はnull-securityです。
以降はテスト部分で発生するが、
Article?
に戻ったオブジェクトにNPEが発生した場合はその部分は無視される.@Repository
class ArticleRepositoryImpl : ArticleRepository {
var sequence: Long = 0L
var store = mutableMapOf<Long, Article>(
++sequence to Article("article1", "content1"),
++sequence to Article("article2", "content2"),
++sequence to Article("article3", "content3"),
)
override fun getArticles() = this.store.values
override fun getArticle(articleId: Long): Article? = this.store.get(articleId)
override fun saveArticle(articleDto: ArticleRequestDto) {
val article = Article(articleDto.title, articleDto.content)
store[++sequence] = article
}
override fun deleteArticle(articleId: Long) {
if (store.containsKey(articleId)) {
store.remove(articleId)
} else {
throw IllegalArgumentException("존재하지 않는 게시글입니다.")
}
}
override fun updateArticle(articleId: Long, articleDto: ArticleRequestDto) {
if (store.containsKey(articleId)) {
store[articleId] = Article(articleDto.title, articleDto.content)
} else {
throw IllegalArgumentException("존재하지 않는 게시글입니다.")
}
}
}
CURDテスト
internal class ArticleRepositoryTest {
private val articleRepository = ArticleRepositoryImpl()
@DisplayName("1. Article 전체조회")
@Test
fun getArticles() {
val articles = articleRepository.getArticles()
assertEquals(3, articles.size)
}
@DisplayName("2. Article 단건조회")
@Test
fun getArticle() {
val article = articleRepository.getArticle(1L)
assertEquals("article1", article?.title)
}
@DisplayName("3. Article 단건조회 (존재하지 않는 id)")
@Test
fun failedGetArticle() {
val article = articleRepository.getArticle(4L)
assertEquals(null, article?.title)
}
@Test
@DisplayName("4. Article 추가")
fun addArticle() {
val articleDto = ArticleRequestDto("article4", "content4")
articleRepository.saveArticle(articleDto)
assertEquals(4, articleRepository.store.size)
}
@Test
@DisplayName("5. Article 삭제")
fun deleteArticle() {
// Given
val deleteArticleId = 1L
// When
articleRepository.deleteArticle(deleteArticleId)
// Then
assertEquals(2, articleRepository.store.size)
}
@Test
@DisplayName("6. Article 수정")
fun updateArticle() {
// Given
val updateArticleId = 1L
val updateArticleDto = ArticleRequestDto("updatedTitle", "updatedContent")
// When
articleRepository.updateArticle(updateArticleId, updateArticleDto)
// Then
assertEquals(3, articleRepository.store.size)
assertEquals("updatedTitle", articleRepository.store[updateArticleId]?.title)
}
}
📌 実装サービス層
色
文法的には本当によかったです.
@Service
class ArticleService(private val articleRepository : ArticleRepositoryImpl) {
/**
* 전체 Article 조회
*/
fun getArticles() = articleRepository.getArticles()
/**
* Article 단건조회
*/
fun getArticle(articleId: Long) = articleRepository.getArticle(articleId)
/**
* Article 추가
*/
fun saveArticle(articleDto: ArticleRequestDto) = articleRepository.saveArticle(articleDto)
/**
* Article 삭제
*/
fun deleteArticle(articleId: Long) = articleRepository.deleteArticle(articleId)
/**
* Article 수정
*/
fun updateArticle(articleId: Long, articleDto: ArticleRequestDto) = articleRepository.updateArticle(articleId, articleDto)
}
📌 コントローラ層実装
@RequestMapping("/api/articles")
@RestController
class ArticleController(
private val articleService: ArticleService
) {
@GetMapping
fun getArticles() = articleService.getArticles()
@GetMapping("/{articleId}")
fun getArticle(@PathVariable articleId: Long) = articleService.getArticle(articleId)
@PostMapping
fun postArticle(@RequestBody articleDto : ArticleRequestDto) : ResponseEntity<Any> {
articleService.saveArticle(articleDto)
return ResponseEntity(HttpStatus.CREATED)
}
@DeleteMapping("/{articleId}")
fun deleteArticle(@PathVariable articleId: Long) = articleService.deleteArticle(articleId)
@PutMapping("/{articleId}")
fun updateArticle(
@PathVariable articleId: Long,
@RequestBody articleDto: ArticleRequestDto
) = articleService.updateArticle(articleId, articleDto)
}
コントローラテスト
@AutoConfigureMockMvc
@SpringBootTest
internal class ArticleControllerTest {
@Autowired
lateinit var mockMvc: MockMvc
@DisplayName("Article 전체조회 API")
@Test
fun 전체조회() {
mockMvc.get("/api/articles")
.andExpect {
content { contentType(MediaType.APPLICATION_JSON) }
status { isOk() }
}
.andDo {
print()
}
}
@DisplayName("Article 단건조회 API")
@Test
fun 단건조회() {
mockMvc.get("/api/articles/{articleId}", 1L)
.andExpect {
status { isOk()}
content {contentType(MediaType.APPLICATION_JSON)}
jsonPath("$.title") { "article1" }
}.andDo {
print()
}
}
@DisplayName("Article 저장 API")
@Test
fun 추가() {
val articleDto = ArticleRequestDto("article4", "content4")
val articleDtoJson:String = Gson().toJson(articleDto)
mockMvc.post("/api/articles") {
content = articleDtoJson
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status { isCreated() }
}.andDo {
print()
}
}
@DisplayName("Article 삭제 API")
@Test
fun 삭제() {
mockMvc.delete("/api/articles/{articleId}", 3L)
.andExpect {
status { isOk() }
}
.andDo { print() }
}
@Test
@DisplayName("Article 수정 API")
fun 수정() {
val updateRequestDto = ArticleRequestDto("updatedTitle", "updatedContent")
val updateRequestDtoJosn = Gson().toJson(updateRequestDto)
mockMvc.put("/api/articles/{articleId}", 1L)
{
contentType = MediaType.APPLICATION_JSON
content = updateRequestDtoJosn
}
.andDo { print() }
.andExpect {
status { isOk() }
}
}
}
Kotlin .. 第一印象よかったです.Reference
この問題について(🔥 TIL-Day 70 KotlinとSpringboot 01 CRUD Rest API実装とJunitテスト), 我々は、より多くの情報をここで見つけました https://velog.io/@dhk22/TIL-Day-70-Kotiln-01テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol