Skip to content

Commit

Permalink
Implement tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nosix committed Nov 3, 2020
1 parent 2eb4a43 commit 199a23f
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 88 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ JWT

Retrofit
- https://stackoverflow.com/questions/41078866/retrofit2-authorization-global-interceptor-for-access-token
- https://github.com/square/retrofit/issues/1554

Kotlinx Serialization
- https://github.com/Kotlin/kotlinx.serialization/issues/678
6 changes: 3 additions & 3 deletions backend-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ repositories {

dependencies {
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0")
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0")
api("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.4.2")
}

Expand Down
17 changes: 17 additions & 0 deletions backend-api/src/main/kotlin/com/example/WebServiceFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Response
import retrofit2.Retrofit
import java.lang.reflect.Type
import kotlin.reflect.KClass

class WebServiceFactory(baseUrl: String) {
Expand All @@ -28,6 +31,7 @@ class WebServiceFactory(baseUrl: String) {

@OptIn(ExperimentalSerializationApi::class)
private val retrofit = Retrofit.Builder()
.addConverterFactory(NullOnEmptyConverterFactory)
.addConverterFactory(Json.asConverterFactory(MediaType.get("application/json")))
.baseUrl(baseUrl)
.client(client)
Expand All @@ -49,4 +53,17 @@ class WebServiceFactory(baseUrl: String) {
fun reset() {
securityContext.authHeader = null
}

private object NullOnEmptyConverterFactory : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, Any?>? {
val delegate = retrofit.nextResponseBodyConverter<Any?>(this, type, annotations)
return Converter<ResponseBody, Any?> {
if (it.contentLength() == 0L) null else delegate.convert(it)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import retrofit2.http.Query

interface CustomerService {
@GET("customers")
suspend fun getAllCustomer(): List<Customer>
suspend fun getAllCustomers(): List<Customer>

@GET("customers/{id}")
suspend fun getCustomer(@Path("id") id: Long): Customer?

@GET("customers/search")
suspend fun getCustomerByLastName(
suspend fun getCustomersByLastName(
@Query("lastName") lastName: String
): List<Customer>

Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dependencies {
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class CustomerController(
private fun Customer.asEntity(): CustomerEntity = CustomerEntity(id, firstName, lastName)

@GetMapping("customers")
override suspend fun getAllCustomer(): List<Customer> {
override suspend fun getAllCustomers(): List<Customer> {
return repository.findAll().asApi()
}

Expand All @@ -37,7 +37,7 @@ class CustomerController(
}

@GetMapping("customers/search")
override suspend fun getCustomerByLastName(
override suspend fun getCustomersByLastName(
@Param("lastName") lastName: String
): List<Customer> {
return repository.findByLastName(lastName).asApi()
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
logging:
level:
root: DEBUG
root: INFO

This file was deleted.

146 changes: 146 additions & 0 deletions backend/src/test/kotlin/com/example/backend/CustomerApiTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.example.backend

import com.example.WebServiceFactory
import com.example.api.AuthenticationService
import com.example.api.Credentials
import com.example.api.Customer
import com.example.api.CustomerService
import com.example.backend.repository.CustomerRepository
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.Arguments.arguments
import org.junit.jupiter.params.provider.MethodSource
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.HttpStatus
import retrofit2.HttpException
import java.util.stream.Stream
import com.example.backend.entity.Customer as CustomerEntity

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class CustomerApiTests {

@MockBean
private lateinit var customerRepository: CustomerRepository

private val factory = WebServiceFactory("http://localhost:8080/")
private val customerService = factory.create<CustomerService>()

@BeforeEach
fun setUp() {
factory.reset()
}

private suspend fun authenticate() {
factory.authenticate {
factory.create<AuthenticationService>()
.login(Credentials("user", "user_password"))
}
}

private suspend fun authenticateAsAdmin() {
factory.authenticate {
factory.create<AuthenticationService>()
.login(Credentials("admin", "admin_password"))
}
}

@Suppress("unused")
companion object {

// MethodSource

@JvmStatic
fun getAllCustomers(): Stream<Arguments> = Stream.of(
listOf(),
listOf(Customer(1, "F1", "L1"), Customer(2, "F2", "L2"))
).map { customers ->
arguments(
customers.map { CustomerEntity(it.id, it.firstName, it.lastName) },
customers
)
}

@JvmStatic
fun getCustomer(): Stream<Arguments> = Stream.of(
null,
Customer(1, "F", "L")
).map { customer ->
arguments(
customer?.let { CustomerEntity(it.id, it.firstName, it.lastName) },
customer
)
}

@JvmStatic
fun postCustomer(): Stream<Arguments> = Stream.of(
Customer(null, "F", "L"),
Customer(1, "F", "L"),
).map { customer ->
arguments(customer)
}
}

@ParameterizedTest
@MethodSource
fun getAllCustomers(entities: List<CustomerEntity>, expected: List<Customer>): Unit = runBlocking {
authenticate()
Mockito
.`when`(customerRepository.findAll())
.thenReturn(entities.asFlow())
val customers = customerService.getAllCustomers()
assertEquals(expected, customers)
}

@ParameterizedTest
@MethodSource
fun getCustomer(entity: CustomerEntity?, expected: Customer?): Unit = runBlocking {
authenticate()
Mockito
.`when`(customerRepository.findById(1))
.thenReturn(entity)
val customer = customerService.getCustomer(1)
assertEquals(expected, customer)
}

@ParameterizedTest
@MethodSource("getAllCustomers")
fun getCustomersByLastName(entities: List<CustomerEntity>, expected: List<Customer>): Unit = runBlocking {
authenticate()
Mockito
.`when`(customerRepository.findByLastName("Last"))
.thenReturn(entities.asFlow())
val customers = customerService.getCustomersByLastName("Last")
assertEquals(expected, customers)
}

@ParameterizedTest
@MethodSource
fun postCustomer(customer: Customer): Unit = runBlocking {
authenticate()
try {
customerService.postCustomer(customer)
fail()
} catch (e: HttpException) {
assertEquals(HttpStatus.FORBIDDEN.value(), e.code())
}
}

@ParameterizedTest
@MethodSource("postCustomer")
fun postCustomerAsAdmin(customer: Customer): Unit = runBlocking {
authenticateAsAdmin()
Mockito
.`when`(customerRepository.save(any(CustomerEntity::class.java)))
.thenReturn(CustomerEntity(customer.id ?: 0, customer.firstName, customer.lastName))
val savedCustomer = customerService.postCustomer(customer)
assertEquals(customer.copy(id = customer.id ?: 0), savedCustomer)
}
}

0 comments on commit 199a23f

Please sign in to comment.