Skip to content

rafaelbercam/APITestsKotlin

Repository files navigation

Badge ServeRest ktlint

Testes de API em Kotlin

Pré-requisitos

  1. Instalar o Kotlin

Ambiente

Para executar os testes localmente, estou utilizando o ServeRest

Logo do ServeRest

Link do Repo: https://github.com/ServeRest/ServeRest

ServeRest está disponível de forma online, no npm e no docker.

Instalando Dependências

Instalar via Gradle

Configuração do Projeto

Estrutura de Pastas

O projeto esta dividido da seguinte maneira:

[APITestsKotlin]
   [src] -> código fonte
        [core] -> setup do requestspecification do REST-assured
        [factory] -> Properties que retornam objetos criados através de uma sererialização
        [requests] -> Métodos que retornam o objeto Response do REST-assured
        [runner] -> Runner do JUnit
        [tests] -> Arquivos de teste do JUnit
        .env -> Arquivo com variáveis de ambiente abstraídas pelo dotenv

Detalhamento

core

A classe Setup é responsável por configurar o RequestSpecification do REST-assured antes de realizar as requisições, e para isso usamos a notação BeforeAll do JUnit para antes dos testes rodarem. Isso é possível graças a anotação @TestInstance que nesse caso estamos usando a LifeCycle.PER_CLASS que permite configurar o cliclo de teste do JUnit através desta classe.

E temos também um tearDown que aciona o reset do REST-assured.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
open class Setup {

    val dotenv = dotenv()

    companion object {
        lateinit var requestSpecification: RequestSpecification
    }

    @BeforeAll
    fun setup() {
        val logConfig = LogConfig.logConfig()
            .enableLoggingOfRequestAndResponseIfValidationFails(LogDetail.ALL)
        val config = RestAssuredConfig.config().logConfig(logConfig)

        requestSpecification = RequestSpecBuilder()
            .setBaseUri(dotenv["PROD"])
            .setContentType(ContentType.JSON)
            .setRelaxedHTTPSValidation()
            .setConfig(config)
            .addFilter(RequestLoggingFilter(LogDetail.ALL))
            .addFilter(ResponseLoggingFilter(LogDetail.ALL))
            .addFilter(/* filter = */ AllureRestAssured())
            .build()
    }

    @AfterAll
    fun tearDown() {
        RestAssured.reset()
    }
}

Veja que foi utilizado o dotenv Kotlin para abstrair a urlBase do Serverest que esta no arquivo .env na raiz do projeto

factory

Aqui temos classes capazes de abstrair objetos que serão enviados para as requisições. Para isso foi utilizado o kotlinx.serialization.Serializable.

Essa biblioteca possui processos que são capazes de fazer uma serealização de um objeto para formatos de uso comum entre as aplicações como o JSON.

Então nessas classes foi utilizado Properties que retornam objetos definidos por uma data class no formato definido pela anotação @Serializable

class ProductFactory {
    var faker = Faker()
    val createProduct: Product
        get() {
            val productName = faker.commerce.productName()
            return Product(
                nome = productName,
                preco = faker.random.nextInt(10, 999),
                descricao = "Awesome $productName",
                quantidade = faker.random.nextInt(bound = 9999)
            )
        }
}

@Serializable
data class Product(
    @Required var nome: String,
    @Required var preco: Int,
    @Required var descricao: String,
    @Required var quantidade: Int
)

A ideia de usar essa estratégia é justamente delegar para a própria classe Factory construir os objetos a serem usados nas requisições.

A partir do momento em que você replica parte do código no seu teste, repetindo em outras partes do seu código, a manutenção do seu código irá sofrer muitos pontos de alteração, então use desse formato para trazer uma maior robustez para seus testes.

requests

Aqui estão as classes responsáveis por realizar as requisições dos testes, retornando o objeto Response do REST-assured onde será capaz de realizar uma desserialização do objeto e realizar as asserções dos testes.

Para isso acontecer no Kotlin, foi utilizado a dependência io.restassured.module.kotlin.extensions para usar os verbos Given, When, Then, Extract do REST-assured.

open class LoginRequests : Setup() {

    @Step("login request /login")
    open fun login(login: Login): Response {
        val response =
            Given {
                spec(requestSpecification)
                    .body(Json.encodeToString(login))
            } When {
                post("/login")
            } Then {
            } Extract {
                response()
            }
        return response
    }
}

Repare que extendemos a class Setup para essas classes de reqquisições, para ser capaz de user o requestSpecification que contem a preparação dos nossos testes.

Outro ponto interessante é que passamos por parâmetro exatamente o data class que define o modelo do objeto esperado.

runner

Essa classe serve para controlar quais são as classes ou pacotes que pertencem ao grupo de testes a serem executados. Esses grupos chamamos de @Suite pelo JUnit

@Suite
@SelectClasses(
    LoginTests::class,
    UserTests::class,
    ProductTests::class,
    CartTests::class
)
@SelectPackages("tests")
class AllIntegratedTests {
}

O exemplo esta redundante, mas foi para exemplificar que podemos fazer da seguinte forma:

  • @SelectClasses: onde defino quais classes de teste serão executadas por este runner
  • @SelectPackages: onde defino quais pacotes serão executados.

Podem ser criados outros runners dependendo do tipo de teste, como por exemplo testes de regressão e testes de uma feature específica.

Exemplo de execução com linha de comando do gradle

./gradlew clean test --tests "runner.AllIntegratedTests"

tests

E por ultimo, aqui estão as classes de teste do JUnit

@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class LoginTests: Setup() {

    var request = LoginRequests()
    lateinit var response: Response
    var login = LoginFactory()

    @Test
    @Order(1)
    @DisplayName("Login bem sucedido")
    fun `login succeeded`() {
        response = request.login(login.loginSucceeded)
        assertEquals(HttpStatus.SC_OK, response.statusCode)
        assertEquals("Login realizado com sucesso", response.jsonPath().get("message"))
    }

    @Test
    @Order(2)
    @DisplayName("Login com falha")
    fun `login failed`() {
        response = request.login(login.loginFail)
        assertEquals(HttpStatus.SC_UNAUTHORIZED, response.statusCode)
        assertEquals("Email e/ou senha inválidos", response.jsonPath().get("message"))
    }
}

Para seguir uma sequencia de execução, o JUnit 5 possui uma anotação @TestMethodOrder que tem capacidade de decidir qual será a ordem de execução dos meus testes. No exemplo utilizei o OrderAnnotation::class que é representado pelo anotação @Order. Essa anotação espera um número inteiro na qual seguirá uma ordem crescente de execução.

OBS: Não utilize para criar uma dependência de um cenário para outro, mas sim organizar os reports, analisar os logs, etc.

Allure

Para gerar os reports basta rodar os commandos

./gradlew allureReport e ./gradlew allureServe

ktlint

Foi utilizado o processador de estilização de código ktlint, ele analisa se o código foi desenvolvido seguindo as boas práticas da linguagem Kotlin.

Para verificar se o código esta com algum code smell basta rodar o comando

gradle ktlint

Assim que encontrar os erros, basta rodar o comando abaixo para corrigir pos problemas

gradle ktlintFormat

GitHub Actions

Este projeto conta com a configuração do GitHub Actions e Configuração do GitHub Pages para saber mais pesquise e veja o arquivo de configuração no diretório /.github