Neste artigo, você aprenderá como criar sua primeira API com Spring Boot e Kotlin.
Spring Framework
Spring Framework 🍃, o framework mais popular em Java para desenvolvimento de aplicações. Milhões de desenvolvedores usam Spring todos os dias para criar todos os tipos de aplicações. A primeira versão do Spring em 2003 tinha foco em injeção de dependência, mas o framework cresceu imensamente, a equipe do Spring criou um conjunto de módulos que suportam quase tudo o que um desenvolvedor precisa para criar uma aplicação. Com tanta maturidade, não é de se admirar que o framework seja utilizado nas principais empresas de tecnologia do mundo. Essa é a vantagem de um projeto que está em constante desenvolvimento e aprimoramento há quase 20 anos.
O que é Kotlin
Kotlin é uma linguagem criada pela Jetbrains, a mesma empresa que criou todas as incríveis IDE’s como IntelliJ Idea. A JetBrains não estava satisfeita com Java e as outras linguagens do mercado e decidiu criar Kotlin para ser uma melhoria em relação ao Java. E quando eu digo melhoria é porque Kotlin foi projetado para ser completamente compatível com Java, possibilitando ter uma base de código compartilhada entre essas duas linguagens, permitindo que as empresas façam uma migração gradual do Java.
Kotlin tem se tornado super popular entre os desenvolvedores e de acordo com a StackOverflow 2020 Survey, Kotlin é uma das linguagens mais amadas. Todo esse amor não é à toa. Kotlin possui muitos recursos que tornam o desenvolvimento mais fácil, menos verboso e menos propenso a falhas. Um dos melhores recursos é um sistema nativo para lidar com null de forma mais segura, classes de extensões, funções de alta ordem, e a versão async await da linguagem, chamada coroutines.
Criando projeto
Para começar a criar nosso projeto, usaremos a ferramenta Spring Initializer. Você pode encontrá-la em start.spring.io. Esta é uma ferramenta criada pelo time do spring e é super útil porque já podemos fazer nossa configuração inicial e incluir nossas principais dependências.
Primeiro, você pode encontrar o código fonte completo aqui no meu Github. Para este projeto, usaremos uma configuração um pouco diferente dos outros tutoriais comuns que podemos encontrar com Spring Boot. Vamos usar Gradle como nossa ferramenta de build, mas é claro, como você pode ver, Maven também é uma opção. Escolheremos a versão mais estável do Spring, que é 2.6.0 (quando estou escrevendo este artigo). Você pode alterar os metadados do projeto se quiser ou simplesmente deixar os valores padrão. Eu escolho o Java 11 porque ainda não tenho o Java 17 instalado, mas não fará diferença para este projeto.
Na coluna da direita, temos a opção de incluir nossas dependências iniciais. Para este projeto, incluiremos algumas e, posteriormente, adicionaremos outras dependências manualmente em nosso arquivo Gradle. Vamos começar incluindo:
- Spring Boot DevTools — Existem alguns recursos interessantes que podem nos ajudar durante o desenvolvimento, como reload automático, live reload, e desativação de cache. Muito útil durante o desenvolvimento.
- Spring Web — Precisaremos criar nossas APIs
- Spring JPA e H2 Database — para salvar nossos dados
Como você pode ver nos meus metadados, vou criar um exemplo simulando um banco e faremos uma API de transferência. 💰
IntelliJ Idea
Descompacte o arquivo e abra-o em sua IDE. Eu usarei o IntelliJ Idea Community Edition. Você pode baixá-lo gratuitamente ou usar outra IDE. Algo interessante a mencionar é que se você tiver a versão ultimate do IntelliJ Idea, o Spring Initialiser Tool que acabamos de ver já está embutido na IDE, então você pode fazer toda a criação do projeto diretamente lá.
Gradle file
A primeira coisa que vamos examinar é o nosso arquivo build.gradle.kts
. Você pode encontrá-lo na raiz da pasta do projeto. Este é o arquivo onde podemos gerenciar nossas dependências e, como usamos o Spring Initializer para a configuração inicial, podemos ver que a ferramenta criou este arquivo com nossa configuração e as dependências selecionadas.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.5.7"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.31"
kotlin("plugin.spring") version "1.5.31"
kotlin("plugin.jpa") version "1.5.31"
}
group = "com.backendhorizon"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
Desde que escolhemos usar Kotlin, o Spring Initializer já criou este arquivo de configuração como um arquivo Kotlin. Para as pessoas que não estão familiarizadas com Gradle, estou apontando isso porque também é possível escrever seus arquivos Gradle com Groovy. Como já estamos usando Kotlin, recomendo fortemente usá-lo também com Gradle, pois há algumas vantagens sobre Groovy, como a auto-completação, por exemplo.
Gradle Plugins
Vamos dar uma olhada no bloco de plugins. O Gradle em si não faz muito por padrão, mas oferece opções para nós, desenvolvedores, estendermos e adicionarmos novas funcionalidades às nossas builds. Um ótimo exemplo disso é o bloco “plugins” onde temos plugins que configuram e deixam nosso projeto pronto para uso de forma muito fácil.
Podemos ver que temos alguns plugins e a maioria deles é para fazer uma configuração inicial para Spring e Kotlin. Vamos dar uma olhada na página do Spring Boot Plugin e ver como o plugin é descrito:
Spring Boot facilita a criação de Aplicações Spring autônomas e de nível de produção que você pode “simplesmente executar”. Adotamos uma visão opinativa da plataforma Spring e de bibliotecas de terceiros para que você possa começar com o mínimo de complicações. A maioria das aplicações Spring Boot precisa de muito pouca configuração Spring.
Em outras palavras, configuração fácil para começar a trabalhar no seu projeto imediatamente. Todos os outros plugins fazem algo semelhante, eles preparam e ajudam a gerenciar as dependências do nosso projeto para serem configuradas e integradas da melhor forma.
Abaixo dos plugins, temos os metadados que escolhemos e a versão do Java.
Maven Central é o repositório de onde estamos baixando nossas dependências e, sob o bloco de dependencies, estão listadas todas as bibliotecas que temos no projeto. Posteriormente, retornaremos a este arquivo e incluiremos algumas dependências extras.
Há outro arquivo importante que vale a pena dar uma olhada rápida, que é o nosso arquivo Application.
Application file
Vamos abrir nosso arquivo de Application. No projeto no GitHub, você pode encontrar este arquivo intitulado como BankApplication
, mas isso mudará dependendo do que você escolher em seus metadados. Aqui encontramos a função mais famosa de todas, a função principal, onde a aplicação começa.
package com.backendhorizon.bank
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class BankApplication
fun main(args: Array<String>) {
runApplication<BankApplication>(*args)
}
Podemos ver que no método principal temos essa função runApplication
indicando para rodar com a classe BankApplication
e nessa classe, temos apenas uma única anotação, @SpringBootApplication
. Essa única anotação faz muito por nós.
Vamos dar uma olhada rápida em como a documentação descreve isso:
Indica uma classe de configuração que declara um ou mais métodos
@Bean
e também aciona a auto-configuração e a varredura de componentes. Esta é uma anotação de conveniência que é equivalente a declarar@Configuration
,@EnableAutoConfiguration
e@ComponentScan
.
No próximo capítulo, criaremos nosso primeiro Controller e algo que você perceberá é que não criaremos uma instância do controlador. Isso ocorre porque a anotação @SpringBootApplication
é composta por outras 3 anotações e uma delas é a @ComponentScan
.
Como o nome já sugere, @ComponentScan
irá procurar/varrer nosso projeto em busca de componentes e quando encontrá-los, o Spring criará e gerenciará essa instância para nós. Algo interessante de mencionar é que para um componente ser auto-detectado, ele precisa estar no mesmo nível de pacote ou abaixo da classe de aplicação.
Componentes são classes anotadas com anotações especiais que candidatam essas classes para serem auto-detectadas. Existem algumas anotações diferentes que podem ser usadas para diferentes propósitos como @Component
, @Controller
, @RestController
, @Repository
e @Service
.
A @EnableAutoConfiguration
fará algo semelhante para as bibliotecas do projeto. Ela procurará no nosso classpath, verificará as bibliotecas que estamos usando e carregará uma configuração inicial para nós. Veremos isso com nosso banco de dados. Continue lendo. 😉
package com.backendhorizon.bank.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/transfer")
class TransferController {
@PostMapping("/new")
fun newTransfer() {
System.out.println("Saved")
}
@GetMapping("/all")
fun getAllTransfers(): String {
return "Hello"
}
}
First API
Vamos criar nosso primeiro controlador de API. Crie um pacote chamado controller e nele, uma classe chamada TransferController
. Crie 2 métodos, newTransfer
e getAllTransfers
como o exemplo abaixo.
Vamos entender o que está acontecendo aqui. No topo da nossa classe TransferController
, usamos 2 anotações, @RestController
e @RequestMapping
:
@RestController
: Usando esta anotação, indicamos ao Spring Boot que esta classe é um manipulador web/controlador web. Ela receberá solicitações e retornará respostas aos chamadores. Como é um tipo de “controlador”, este componente (lembre-se deste termo) também será gerenciado pelo Spring Boot.@RequestMapping
: Isso indica qual será a assinatura deste controlador web no endereço da solicitação, após o endereço do host. Por exemplo, nossos 2 métodos endpoints terão os seguintes endereços:localhost:8080/transfer/new
elocahost:8080/transfer/all
.
Seguindo os métodos, eles são relativamente simples:
newTransfer
registra uma mensagem “Saved” no console e sua anotação indica que esta será uma solicitação “Post”,@**Post**Mapping
e, como o controlador, indica qual é a assinatura para este método,localhost:8080/payment/new
.getAllTransfer
retorna uma mensagem “Hello” ao chamador e, semelhante ao método anterior, indica o tipo de solicitação, neste caso, uma solicitação Get,@GetMapping
, seguida pela assinatura,localhost:8080/payment/all
.Get
ePost
fazem parte da especificação REST, se você não está familiarizado com eles, deixe um comentário e eu criarei um novo artigo explicando-os mais detalhadamente.
Running & Testing
Agora temos tudo o que precisamos para fazer nosso primeiro teste. Vá para a função principal no arquivo de aplicação e execute sua aplicação. Você deve começar a ver alguns logs e Tomcat, nosso WebServer, iniciará e hospedará a aplicação sem problemas. Note nos logs que o Tomcat informará em qual porta sua aplicação está sendo executada.
Agora precisamos testar esses endpoints. A melhor maneira é usar uma ferramenta feita para isso. Eu pessoalmente recomendo o Postman. Ele tem suporte completo para APIs REST, possibilitando testar todos os diferentes tipos de endpoints REST, configurar corpos JSON, cabeçalhos, tokens de autorização, etc. O Postman também tem suporte para GraphQL, caso você esteja interessado. 😂
Abra uma nova aba no Postman, selecione GET e digite o endereço do endpoint getAllTransfers
, como na imagem abaixo:
Como você pode ver, recebemos uma mensagem de “Hello” em retorno. Nossa API está funcionando! Faça o mesmo para newTransfer
, altere a URL e o tipo para POST. Você verá que a API também está funcionando e a mensagem “Saved” impressa no console do IntelliJ Idea.
Note que não criamos nenhuma instância do TransferController, apenas a anotação @RestController indicou ao Spring que nosso controlador precisava ser criado. Funciona perfeitamente, não é?!
Modelos de Dados
Vamos começar a preparar nossa aplicação para receber e salvar dados. Dentro do pacote controller, crie um novo chamado model
. Crie duas novas classes, TransactionModel
e OverviewTransactionModel
.
Na classe TransactionModel
vamos criar alguns atributos. Então vamos imaginar que para fazer uma transferência para outra pessoa, precisamos saber qual é o identificador da conta, que pode ser um número ou uma string, dependendo do país em que você mora. Também precisamos do valor e uma descrição que pode ser opcional.
package com.backendhorizon.bank.controller.model
class TransactionModel(
val targetAccount: String,
val value: Double,
val description: String = "",
)
package com.backendhorizon.bank.controller.model
import java.util.*
class OverviewTransactionModel(
val targetAccount: String,
val value: Double,
val description: String,
val date: Date,
val id: String
)
Quando buscamos a lista de todas as transações, gostaríamos de ver um pouco mais de dados. Além de todos os dados anteriores, teremos dois campos extras, a data da transação e o identificador da transação(id).
package com.backendhorizon.bank.controller
import com.backendhorizon.bank.controller.model.OverviewTransactionModel
import com.backendhorizon.bank.controller.model.TransactionModel
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/transfer")
class TransferController {
@PostMapping("/new")
fun newTransfer(@RequestBody transactionModel: TransactionModel) {
// TODO: SAVE THE DATA
}
@GetMapping("/all")
fun getAllTransfer(): List<OverviewTransactionModel> {
// TODO: FETCH THE DATA
return listOf()
}
}
Atualize seu TransferController
como o código abaixo com os novos modelos que criamos. Você pode notar que no método newTransfer
temos uma nova anotação @RequestBody
. Isso indica que o TransactionModel
faz parte desta solicitação e deserializará o JSON automaticamente em nosso modelo.
Database
Agora que estamos recebendo dados através da nossa API, precisamos armazená-los em um banco de dados. Vamos usar um banco de dados in-memory chamado H2 que já incluímos em nosso arquivo build.gradle.kts
.
Crie um novo pacote no mesmo nível do controlador chamado repository
. Dentro deste pacote, uma interface chamada TransferRepository
. Vamos criar também outro pacote model
dentro de repository e nele uma classe chamada TransactionDBModel
. No final, você deve ter algo assim:
Primeiro, vamos trabalhar nos atributos do modelo de banco de dados. Semelhante ao TransactionModel
, teremos o identificador da conta, valor e descrição. Para salvar no banco de dados, precisamos ter um identificador único e esse identificador será gerado automaticamente para nós. Também vamos usar a data atual como a data da transação.
package com.backendhorizon.bank.repository.model
import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
@Entity
class TransactionDBModel(
val accountIdentifier: String,
val value: Double,
val description: String = "",
) {
@Id
@GeneratedValue
var id: UUID? = null
private set
val date: Date = Date()
}
package com.backendhorizon.bank.repository
import com.backendhorizon.bank.repository.model.TransactionDBModel
import org.springframework.data.repository.CrudRepository
import java.util.*
interface TransferRepository: CrudRepository<TransactionDBModel, UUID>
Pelo constructor, vamos receber os atributos que recebemos via nossa API e no corpo da classe, teremos os dois atributos extras (id e data). Nós poderíamos já inicializar o id com um UUID aleatório, mas vamos deixar o JPA fazer isso. Tornamos o set privado para que não possamos substituir esse valor, apenas lê-lo. A data será sempre a data atual, então apenas inicializamos com a data atual.
Nesta classe, temos três novas anotações:
@Entity
: Isso indica que esta classe será usada pelo JPA (Java Persistence API) para salvar os dados em um banco de dados.@Id
: Indica que o campo anotado será a chave primária no banco de dados para esta entidade.@GeneratedValue
: o nome é autoexplicativo. Indica que essa chave primária deve gerar seu próprio valor automaticamente.
Agora que temos nosso modelo, vamos criar nossa interface que fará operações CRUD (Create, Read, Update and Delete). A maneira que vamos fazer isso é estendendo nosso TransferRepository
com CrudRepository
. É incrível como isso é simples e poderoso.
Se você abrir a interface CrudRepository
, é fácil ver o que está acontecendo. Esta interface possui todas as funcionalidades genéricas para CRUD e tudo o que precisamos é indicar qual é o nosso modelo e a chave primária, no nosso caso TransactionDBModel
e UUID
.
Apenas indicando os tipos de modelo e chave primária, o Spring descobrirá que estamos usando esta interface e injetará uma implementação padrão para esta interface (e os tipos que fornecemos). Você pode dar uma olhada na implementação padrão, é uma classe chamada SimpleJpaRepository
.
Claro, não estamos limitados apenas a essas funções. Também é possível criar operações de banco de dados mais complexas, mas para este artigo, trabalharemos apenas com esta implementação padrão.
From API to Database and vice-versa
Agora que nossa camada de persistência está concluída, podemos salvar os dados que estamos recebendo da API. A única coisa que precisamos fazer é uma pequena conversão entre nossos modelos, já que o modelo que recebemos no endpoint é diferente do que usamos no banco de dados. Vamos criar duas funções de extensão para converter esses modelos, convertToDBModel
e convertToOverviewTransactionModel
.
Primeiro em TransactionModel
, converta o modelo do endpoint em um modelo de banco de dados, TransactionDBModel
.
package com.backendhorizon.bank.controller.model
import com.backendhorizon.bank.repository.model.TransactionDBModel
class TransactionModel(
val targetAccount: String,
val value: Double,
val description: String = "",
)
fun TransactionModel.convertToDBModel() = TransactionDBModel(
accountIdentifier = this.targetAccount,
value = this.value,
description = this.description
)
E para a visão geral, algo semelhante. Mas desta vez o modelo de banco de dados TransactionDBModel
para um modelo de endpoint, OverviewTransactionModel
.
package com.backendhorizon.bank.controller.model
import com.backendhorizon.bank.repository.model.TransactionDBModel
import java.util.*
class OverviewTransactionModel(
val targetAccount: String,
val value: Double,
val description: String,
val date: Date,
val id: String
)
fun TransactionDBModel.convertToOverviewTransactionModel() = OverviewTransactionModel(
targetAccount = this.accountIdentifier,
value = this.value,
description = this.description,
date = this.date,
id = this.id.toString()
)
OK, agora podemos converter os dados entre nossos endpoints e o banco de dados. Vamos atualizar nosso controlador para fazer essas chamadas.
No construtor, incluiremos nossa classe TransferRepository
. Lembre-se, o Spring é inteligente, quando o Spring vê que temos essa dependência em nosso controlador, ele irá injetar a implementação criada para TransactionRepository
(baseado em SimpleJpaRepository
).
package com.backendhorizon.bank.controller
import com.backendhorizon.bank.controller.model.TransactionModel
import com.backendhorizon.bank.controller.model.OverviewTransactionModel
import com.backendhorizon.bank.controller.model.convertToDBModel
import com.backendhorizon.bank.controller.model.convertToOverviewTransactionModel
import com.backendhorizon.bank.repository.TransferRepository
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/transfer")
class TransferController(val repository: TransferRepository) {
@PostMapping("/new")
fun newTransfer(@RequestBody transactionModel: TransactionModel) {
repository.save(transactionModel.convertToDBModel())
}
@GetMapping("/all")
fun getAllTransfers(): List<OverviewTransactionModel> {
return repository.findAll().map { it.convertToOverviewTransactionModel() }
}
}
Você pode ver no exemplo acima que nossa classe repositório está usando os métodos genéricos de CrudRepository
para salvar e encontrar todos as transações no banco de dados. Também fazemos uso dos métodos de extensão que criamos para converter o modelo para o mais apropriado.
Neste ponto, nossos endpoints estão todos prontos. Recompile e execute a aplicação. Vamos criar uma nova solicitação de transferência. Lembre-se que agora precisamos incluir todos os dados necessários no Postman para nosso endpoint funcionar corretamente. Inclua um corpo JSON como na imagem abaixo e envie.
Após enviar, tudo deve funcionar bem. Você deve receber um status 200, significando que a solicitação foi um sucesso. Agora, se você buscar todos os dados novamente, verá os dados da transferência submetida, incluindo a data e o id gerado.
Nossa API está funcionando 🎉 mas ainda não terminamos!
Database Console
Há uma maneira muito boa que o H2 Database nos oferece para verificar os dados salvos, e essa é a “H2 Console”. É um administrador web onde você pode verificar os dados do banco de dados e executar consultas.
Primeiro, para habilitar este console, você precisa adicionar o seguinte código no seu arquivo application.properties
dentro da pasta de recursos.
# Enabling H2 Console
spring.h2.console.enabled=true
Depois de adicionar, execute sua aplicação novamente e entre no seu navegador na URL http://localhost:8080/h2-console
. Você verá uma janela de login para conectar ao seu banco de dados. A única coisa que você precisa mudar é a JDBC Url. Olhe nos logs do console e procure por “H2 console available at …” como na imagem abaixo.
Copie a URL do banco de dados e pressione connect. Você deverá ver o console. Agora você pode brincar com seu banco de dados, rodar consultas SQL e ver os dados sendo salvos pela sua API.
Agora terminamos, certo? ERRADO! Somos bons desenvolvedores e bons desenvolvedores escrevem testes!
Unit tests
Antes de escrever nosso primeiro teste, vamos adicionar uma nova dependência no bloco de dependencies dentro do build.gradle.kts
. Com essa dependência, em vez de Mockito, vamos usar Mockk para criar instâncias mock.
// ... other dependencies already included
testImplementation("com.ninja-squad:springmockk:3.0.1")
Esta biblioteca nos ajuda a dar um melhor suporte ao Mockk no contexto do Spring. Você pode ler mais sobre isso aqui.
package com.backendhorizon.bank.controller
import com.fasterxml.jackson.databind.ObjectMapper
import com.backendhorizon.bank.controller.model.TransactionModel
import com.backendhorizon.bank.repository.TransferRepository
import com.backendhorizon.bank.repository.model.TransactionDBModel
import com.ninjasquad.springmockk.MockkBean
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import java.util.*
@WebMvcTest
class PaymentsControllerTest(@Autowired val mockMvc: MockMvc) {
@MockkBean
private lateinit var paymentsRepository: TransferRepository
@Test
fun `should submit transaction with success`() {
val transfer = TransactionModel(
value = 1.50,
description = "store 1",
targetAccount = "NL76ABNA2376059879"
)
every { paymentsRepository.save(any()) } returns mockk()
mockMvc.perform(post("/transfer/new")
.content(ObjectMapper().writeValueAsString(transfer))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk)
}
@Test
fun `should get transaction with success`() {
val mockTransaction = mockk<TransactionDBModel>().apply {
every { value } returns 1.50
every { description } returns "store 1"
every { accountIdentifier } returns "NL47INGB8845464385"
every { date } returns Date()
every { id } returns UUID.randomUUID()
}
every { paymentsRepository.findAll() } returns listOf(mockTransaction)
mockMvc.perform(get("/transfer/all").accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("\$.[0].description").value("store 1"))
}
@Test
fun `should return a bad request error if the request there's no body`() {
mockMvc.perform(post("/transfer/new")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().is4xxClientError)
}
}
Crie uma nova classe chamada PaymentsControllerTest
dentro da pasta de testes. Tire um momento para olhar os testes abaixo e entenderemos o que está acontecendo.
A ideia geral do que os testes estão fazendo é fácil de entender graças ao framework Spring Testing, que é muito expressivo. Vamos dar uma olhada nas tecnicalidades:
@WebMvcTest
: Esta anotação indica que esses testes estão testando um Componente MVC, neste caso, um controlador REST.MockMVC
: Este é o ponto de entrada que nos permitirá comunicar com nosso controlador durante os testes, fazendo requisições e verificando o resultado e os status.@MockkBean
: Estamos simulando uma classeTransferRepository
falsa para simular diferentes cenários. Simulamos esses cenários sempre antes de realizar uma ação comMockMvc
, simulando o resultado dos métodosfindAll
esave
.
Depois de configurarmos nossos mocks, realizamos uma solicitação ao controlador usando MockMvc
. Todos os testes seguem uma estrutura semelhante:
- Dizemos ao
MockMvc
para realizar uma solicitação e qual tipo é (post ou get). - Se tivermos um corpo na solicitação, transformamos o objeto em um JSON.
- O tipo de conteúdo da solicitação,
APPLICATION_JSON
. - Imprimimos no console os detalhes e resultados da solicitação.
- E finalmente, declaramos o que esperamos desta solicitação. Pode ser uma verificação de código de status se a solicitação for bem-sucedida ou uma resposta JSON (e o conteúdo).
E finalmente terminamos. 🎉🎉🎉
Você pode encontrar o projeto completo no GitHub.
Perguntas ou sugestões, por favor deixe um comentário, estamos todos aqui para aprender juntos. 🤠
Obrigado por ler.