OK
Spring + Kotlin 3: testowanie poszczególnych elementów

Spring + Kotlin 3: testowanie poszczególnych elementów

Spring + Kotlin 3: testowanie poszczególnych elementów

Poprzedni odcinek:

Tym razem przyjrzymy się bliżej poszczególnym częściom Kotlinowego DSLa do tworzenia RESta w najnowszym Springu i zobaczymy jak można wydzielić poszczególne jego części by móc lepiej je testować.

Handler – logika obsługi requestu

Mapowanie konkretnego requestu ma postać

[{PREDICATE} , {HANDLER}]

Gdzie Handler określa co ma się stać gdy już dopasujemy dany request na podstawie predykatu. Co nas tutaj najbardziej interesuje to łatwość z jaką można ów handler zdefiniować poza definicją routera aby nie skończyć z ogromny zagnieżdżeniem kodu w miejscu mapowania.

Deklaracja wygląda następująco:

val helloHandler: (ServerRequest) -> Mono<ServerResponse> =
{ ServerResponse.ok().body(Mono.just("Example3"), String::class.java) }

Jest to prosta funkcja, bardzo wygodna do testowania. Argument wejściowy typu ServerRequest może być zmockowany lub – jeśli bardzo chcesz – możesz stworzyć własną implementację do testów jakkolwiek pamiętaj, ze tam jest kilkanaście metod do zaimplementowania.

val r= object : ServerRequest{....}
val r2= mock<ServerRequest>()
helloHandler(r)

W sytuacji gdy nie musisz wołać żadnej blokującej metody w handlerze wtedy użyj prostszej – synchronicznej wersji .

val helloHandler2: (ServerRequest) -> Mono<ServerResponse> =
{ ServerResponse.ok().syncBody("Example3") }

Predykat – dopasowanie requestu

Z predykatami sprawa na pozór jest bardziej skomplikowana gdyż ‘GET’ użyte w routerze jest tak naprawdę metodą lokalną danego DSLa, w związku z czym potrzebuje działać w ramach jakiejś stworzonej instancji tego typu.Jednakże po bliższym zapoznaniu się z metoda widzimy, iż jest to zwykły forward do metody statycznej:

//To jest Kotlin DSL
fun GET(pattern: String): RequestPredicate = RequestPredicates.GET(pattern)


//a to już Java
public static RequestPredicate GET(String pattern) {
  return method(HttpMethod.GET).and(path(pattern));
}

W związku z tym można je łatwo i wygodnie deklarować poza funkcją ‘router’

val finalPredicate=(RequestPredicates.GET("/example3").or(RequestPredicates.GET("/hello3")))

Innym jeszcze rozwiązaniem jest zadeklarowanie metody, która przyjmie używany DSL i stworzy GET w jego kontekście :

fun composePredicate(dsl:RouterFunctionDsl) = (dsl.GET("/example3").or(dsl.GET("/hello3")))


//I w routerze
router {
   composePredicate(this)
…
}

Kompozycja

Kiedy mamy już zdefiniowany zarówno ‘predicate’ jak i ‘handler’ kompozycja tych dwóch typów jest banalna.

router {
   requestPredicate.invoke(helloHandler)
....

lub

router {
   requestPredicate(helloHandler)
...

Jednakże kryje się tutaj pewna rzecz warta wyjaśnienia. Otóż nie znajdziesz metody ‘invoke’ w klasie ‘RequestPredicate’ . Jest to tzw. ‘Extension method’ ale zadeklarowane lokalnie w DSL!

operator fun RequestPredicate.invoke(f: (ServerRequest) -> Mono<ServerResponse>) {
  routes += RouterFunctions.route(this, HandlerFunction { f(it) })
}

Ma to sens gdyż zauważ, iż wynikiem tej metody jest Unit a zbudowana ‘RouterFunction’ jest zapisana w lokalnym stanie DSLa. Czyli takie ciekawe “domknięcie” stanu wokół funkcji.

open class RouterFunctionDsl {

  private val routes = mutableListOf<RouterFunction<ServerResponse>>()
…
}

Podsumowując możemy powiedzieć, iż nowy sposób deklaracji w Spring 5 daje nowe możliwości jednostkowego testowania poszczególnych komponentów bez potrzeby znajomości kontekstu i dodatkowych Test Runnerów.

ul. Jaracza 62
90-251 Łódź
Bitnami