mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
add tests
This commit is contained in:
@@ -5,6 +5,7 @@ import com.example.demo.services.kafka.ProducerImpl
|
|||||||
import com.example.demo.services.kafka.dto.serializer.ProductSerializer
|
import com.example.demo.services.kafka.dto.serializer.ProductSerializer
|
||||||
import org.apache.kafka.clients.producer.ProducerConfig
|
import org.apache.kafka.clients.producer.ProducerConfig
|
||||||
import org.apache.kafka.common.serialization.StringSerializer
|
import org.apache.kafka.common.serialization.StringSerializer
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
@@ -34,7 +35,7 @@ class ProducerConfig(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun producer(): Producer = ProducerImpl(
|
fun producer(@Autowired kafkaTemplate: KafkaTemplate<String, Any>): Producer = ProducerImpl(
|
||||||
kafkaTemplate(),
|
kafkaTemplate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -15,5 +15,5 @@ import org.testcontainers.junit.jupiter.Testcontainers
|
|||||||
@EnableJdbcRepositories
|
@EnableJdbcRepositories
|
||||||
class BaseFeatureTest {
|
class BaseFeatureTest {
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var producer: Producer
|
lateinit var producer: Producer
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|||||||
import org.hamcrest.Matchers.contains
|
import org.hamcrest.Matchers.contains
|
||||||
import org.hamcrest.Matchers.nullValue
|
import org.hamcrest.Matchers.nullValue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.kotlin.doReturn
|
|
||||||
import org.mockito.kotlin.eq
|
import org.mockito.kotlin.eq
|
||||||
import org.mockito.kotlin.verifyNoInteractions
|
import org.mockito.kotlin.verifyNoInteractions
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
@@ -48,7 +47,8 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
|
|
||||||
whenever(productService.findByGuid(
|
whenever(productService.findByGuid(
|
||||||
eq(guid),
|
eq(guid),
|
||||||
)) doReturn product
|
))
|
||||||
|
.thenReturn(product)
|
||||||
|
|
||||||
mockMvc.get("/api/product/$guid")
|
mockMvc.get("/api/product/$guid")
|
||||||
.andExpect { status { status { isOk() } } }
|
.andExpect { status { status { isOk() } } }
|
||||||
@@ -66,7 +66,8 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
|
|
||||||
whenever(productService.findByGuid(
|
whenever(productService.findByGuid(
|
||||||
eq(guid),
|
eq(guid),
|
||||||
)) doReturn null
|
))
|
||||||
|
.thenReturn(null)
|
||||||
|
|
||||||
mockMvc.get("/api/product/$guid")
|
mockMvc.get("/api/product/$guid")
|
||||||
.andExpect { status { status { isNotFound() } } }
|
.andExpect { status { status { isNotFound() } } }
|
||||||
@@ -89,16 +90,17 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
eq(name),
|
eq(name),
|
||||||
eq(price),
|
eq(price),
|
||||||
eq(description)
|
eq(description)
|
||||||
)) doReturn Product(
|
))
|
||||||
id = productId,
|
.thenReturn(Product(
|
||||||
guid = UUID.randomUUID(),
|
id = productId,
|
||||||
name = name,
|
guid = UUID.randomUUID(),
|
||||||
description = description,
|
name = name,
|
||||||
price = price,
|
description = description,
|
||||||
createdAt = OffsetDateTime.now(),
|
price = price,
|
||||||
updatedAt = null,
|
createdAt = OffsetDateTime.now(),
|
||||||
deletedAt = null,
|
updatedAt = null,
|
||||||
)
|
deletedAt = null,
|
||||||
|
))
|
||||||
|
|
||||||
mockMvc.post("/api/product") {
|
mockMvc.post("/api/product") {
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
@@ -118,8 +120,6 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
mapOf("description" to description, "price" to price)
|
mapOf("description" to description, "price" to price)
|
||||||
)
|
)
|
||||||
|
|
||||||
verifyNoInteractions(productService)
|
|
||||||
|
|
||||||
mockMvc.post("/api/product") {
|
mockMvc.post("/api/product") {
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
content = reqBody
|
content = reqBody
|
||||||
@@ -128,6 +128,8 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
|
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
|
||||||
.andExpect { jsonPath("\$.status") { value(ResponseStatus.BAD_REQUEST.status) } }
|
.andExpect { jsonPath("\$.status") { value(ResponseStatus.BAD_REQUEST.status) } }
|
||||||
.andExpect { jsonPath("\$.cause") { contains("name") } }
|
.andExpect { jsonPath("\$.cause") { contains("name") } }
|
||||||
|
|
||||||
|
verifyNoInteractions(productService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -139,8 +141,6 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
mapOf("name" to "", "description" to description, "price" to price)
|
mapOf("name" to "", "description" to description, "price" to price)
|
||||||
)
|
)
|
||||||
|
|
||||||
verifyNoInteractions(productService)
|
|
||||||
|
|
||||||
mockMvc.post("/api/product") {
|
mockMvc.post("/api/product") {
|
||||||
contentType = MediaType.APPLICATION_JSON
|
contentType = MediaType.APPLICATION_JSON
|
||||||
content = reqBody
|
content = reqBody
|
||||||
@@ -149,6 +149,8 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
|
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
|
||||||
.andExpect { jsonPath("\$.status") { value(ResponseStatus.UNPROCESSABLE.status) } }
|
.andExpect { jsonPath("\$.status") { value(ResponseStatus.UNPROCESSABLE.status) } }
|
||||||
.andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } }
|
.andExpect { jsonPath("\$.cause") { value(MethodArgumentNotValidException::class.qualifiedName) } }
|
||||||
|
|
||||||
|
verifyNoInteractions(productService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -157,16 +159,17 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
|
|||||||
|
|
||||||
whenever(productService.delete(
|
whenever(productService.delete(
|
||||||
eq(guid),
|
eq(guid),
|
||||||
)) doReturn Product(
|
))
|
||||||
id = 2133,
|
.thenReturn(Product(
|
||||||
guid = guid,
|
id = 2133,
|
||||||
name = "name",
|
guid = guid,
|
||||||
description = "description",
|
name = "name",
|
||||||
price = 210202,
|
description = "description",
|
||||||
createdAt = OffsetDateTime.now(),
|
price = 210202,
|
||||||
updatedAt = null,
|
createdAt = OffsetDateTime.now(),
|
||||||
deletedAt = OffsetDateTime.now(),
|
updatedAt = null,
|
||||||
)
|
deletedAt = OffsetDateTime.now(),
|
||||||
|
))
|
||||||
|
|
||||||
mockMvc.delete("/api/product/${guid}")
|
mockMvc.delete("/api/product/${guid}")
|
||||||
.andExpect { status { status { isOk() } } }
|
.andExpect { status { status { isOk() } } }
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.example.demo.services
|
||||||
|
|
||||||
|
import com.example.demo.BaseFeatureTest
|
||||||
|
import com.example.demo.exceptions.NotFoundException
|
||||||
|
import com.example.demo.exceptions.UnprocessableException
|
||||||
|
import com.example.demo.models.Product
|
||||||
|
import com.example.demo.provider.ProductRepository
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
@ContextConfiguration(classes = [ProductRepository::class])
|
||||||
|
class ProductServiceImplFeatureTest: BaseFeatureTest() {
|
||||||
|
private lateinit var productService: ProductServiceImpl
|
||||||
|
@Autowired
|
||||||
|
private lateinit var productRepository: ProductRepository
|
||||||
|
|
||||||
|
@BeforeTest
|
||||||
|
fun setUp() {
|
||||||
|
productService = ProductServiceImpl(
|
||||||
|
defaultSyncTopic = "some-default-topic",
|
||||||
|
productRepository = productRepository,
|
||||||
|
producer = producer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun createFindDelete_success() {
|
||||||
|
val name = "new-product-name"
|
||||||
|
val price = 33333.toLong()
|
||||||
|
val description = "some-description"
|
||||||
|
var product: Product? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
product = productService.create(name = name, price = price, description = description)
|
||||||
|
assertNotNull(product.id)
|
||||||
|
assertEquals(name, product.name)
|
||||||
|
assertEquals(price, product.price)
|
||||||
|
assertEquals(333.33, product.getPriceDouble())
|
||||||
|
|
||||||
|
val dbProduct = productService.findByGuid(product.guid)
|
||||||
|
assertNotNull(dbProduct)
|
||||||
|
assertEquals(product.id, dbProduct.id)
|
||||||
|
assertFalse(dbProduct.isDeleted())
|
||||||
|
|
||||||
|
val deletedProduct = productService.delete(product.guid)
|
||||||
|
assertNotNull(deletedProduct)
|
||||||
|
assertEquals(product.id, deletedProduct.id)
|
||||||
|
assertNotNull(deletedProduct.deletedAt)
|
||||||
|
assertTrue(deletedProduct.isDeleted())
|
||||||
|
|
||||||
|
// try to delete already deleted product
|
||||||
|
assertThrows<UnprocessableException> {
|
||||||
|
productService.delete(product.guid)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows<NotFoundException> {
|
||||||
|
productService.delete(UUID.randomUUID())
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
val id = product?.id
|
||||||
|
if (id != null) {
|
||||||
|
productRepository.deleteById(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +1,102 @@
|
|||||||
package com.example.demo.services
|
package com.example.demo.services
|
||||||
|
|
||||||
import com.example.demo.BaseFeatureTest
|
import com.example.demo.exceptions.NotFoundException
|
||||||
import com.example.demo.models.Product
|
import com.example.demo.models.Product
|
||||||
import com.example.demo.provider.ProductRepository
|
import com.example.demo.provider.ProductRepository
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import com.example.demo.services.kafka.Producer
|
||||||
import org.springframework.test.context.ContextConfiguration
|
import com.example.demo.services.kafka.exceptions.InvalidArgumentException
|
||||||
import kotlin.test.*
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.BeforeTest
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
@ContextConfiguration(classes = [ProductRepository::class, ProductServiceImpl::class])
|
@RunWith(SpringRunner::class)
|
||||||
class ProductServiceImplTest: BaseFeatureTest() {
|
@SpringBootTest
|
||||||
@Autowired
|
class ProductServiceImplTest {
|
||||||
|
private val defaultTopic = "some-default-topic"
|
||||||
private lateinit var productService: ProductServiceImpl
|
private lateinit var productService: ProductServiceImpl
|
||||||
@Autowired
|
|
||||||
|
@MockBean
|
||||||
|
@Qualifier("producer")
|
||||||
|
private lateinit var producer: Producer
|
||||||
|
@MockBean
|
||||||
private lateinit var productRepository: ProductRepository
|
private lateinit var productRepository: ProductRepository
|
||||||
|
|
||||||
|
@BeforeTest
|
||||||
|
fun setUp() {
|
||||||
|
productService = ProductServiceImpl(
|
||||||
|
defaultSyncTopic = defaultTopic,
|
||||||
|
productRepository = productRepository,
|
||||||
|
producer = producer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun createFindDelete_success() {
|
fun syncToKafka_success() {
|
||||||
val name = "new-product-name"
|
val guid = UUID.randomUUID()
|
||||||
val price = 33333.toLong()
|
val product = Product(
|
||||||
val description = "some-description"
|
id = 123,
|
||||||
var product: Product? = null
|
guid = guid,
|
||||||
|
name = "name",
|
||||||
|
description = "description",
|
||||||
|
price = 10050,
|
||||||
|
createdAt = OffsetDateTime.now().minusDays(1),
|
||||||
|
updatedAt = OffsetDateTime.now().minusHours(2),
|
||||||
|
deletedAt = OffsetDateTime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
whenever(productRepository.findByGuid(eq(guid)))
|
||||||
product = productService.create(name = name, price = price, description = description)
|
.thenReturn(product)
|
||||||
assertNotNull(product.id)
|
whenever(producer.produceProductInfo(defaultTopic, product)).doAnswer{}
|
||||||
assertEquals(name, product.name)
|
|
||||||
assertEquals(price, product.price)
|
|
||||||
assertEquals(333.33, product.getPriceDouble())
|
|
||||||
|
|
||||||
val dbProduct = productService.findByGuid(product.guid)
|
productService.syncToKafka(guid, null)
|
||||||
assertNotNull(dbProduct)
|
}
|
||||||
assertEquals(product.id, dbProduct.id)
|
|
||||||
assertFalse(dbProduct.isDeleted())
|
|
||||||
|
|
||||||
val deletedProduct = productService.delete(product.guid)
|
@Test
|
||||||
assertNotNull(deletedProduct)
|
fun syncToKafka_notFound() {
|
||||||
assertEquals(product.id, deletedProduct.id)
|
val specificTopic = "specificNotice"
|
||||||
assertNotNull(deletedProduct.deletedAt)
|
val guid = UUID.randomUUID()
|
||||||
assertTrue(deletedProduct.isDeleted())
|
|
||||||
} finally {
|
|
||||||
val id = product?.id
|
|
||||||
if (id != null) {
|
|
||||||
productRepository.deleteById(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
whenever(productRepository.findByGuid(eq(guid)))
|
||||||
|
.thenReturn(null)
|
||||||
|
|
||||||
|
assertThrows<NotFoundException> {
|
||||||
|
productService.syncToKafka(guid, specificTopic)
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyNoInteractions(producer)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun syncToKafka_invalidArgumentException() {
|
||||||
|
val specificTopic = "specificNotice"
|
||||||
|
val guid = UUID.randomUUID()
|
||||||
|
|
||||||
|
val product = Product(
|
||||||
|
id = 123,
|
||||||
|
guid = guid,
|
||||||
|
name = "name",
|
||||||
|
description = "description",
|
||||||
|
price = 10050,
|
||||||
|
createdAt = OffsetDateTime.now().minusDays(1),
|
||||||
|
updatedAt = OffsetDateTime.now().minusHours(2),
|
||||||
|
deletedAt = OffsetDateTime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
whenever(productRepository.findByGuid(eq(guid)))
|
||||||
|
.thenReturn(product)
|
||||||
|
whenever(producer.produceProductInfo(specificTopic, product))
|
||||||
|
.doThrow(InvalidArgumentException("some error"))
|
||||||
|
|
||||||
|
assertThrows< InvalidArgumentException> {
|
||||||
|
productService.syncToKafka(guid, specificTopic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user