Abaixo estão os requisitos cumpridos nesse exercício de criação de um CRUD(Create, Read, Update, Delete) de uma API.
- Clone o repositório
- Use o comando:
git clone git@github.com:tryber/acc-csharp-0x-exercises-customer-crud.git
. - Entre na pasta do repositório que você acabou de clonar:
cd acc-csharp-0x-exercises-customer-crud
- Instale as dependências
- Entre na pasta com
cd src
. - Execute o comando:
dotnet restore
.
- Subindo a API
- Entre na pasta
cd CustomerCrud
. - Execute o comando:
dotnet run
.
- Rodando os testes
- Entre na pasta
cd CustomerCrud.Test
. - Execute o comando:
dotnet test
.
🛠 Testes
O .NET já possui sua própria plataforma de testes.
Este projeto já vem configurado e com suas dependências.
Para executar os testes com o .NET, execute o comando dentro do diretório do seu projeto src/<project>
ou de seus testes src/<project>.Test
!
dotnet test
Para executar um teste específico, basta executar o comando dotnet test --filter Name~TestMethod1
.
TestMethod1
.
- Algumas opções que podem lhe ajudar são:
-?|-h|--help
: exibe a descrição completa de como utilizar o comando.-t|--list-tests
: lista todos os testes, ao invés de executá-los.-v|--verbosity <LEVEL>
: define o nível de detalhe na resposta dos testes.q | quiet
m | minimal
n | normal
d | detailed
diag | diagnostic
- Exemplo de uso:
ou
dotnet test -v diag
dotnet test --verbosity=diagnostic
Agora que você já sabe como criar uma API básica, com controllers funcionais que implementam um CRUD, que tal testarmos isso na prática?
Vamos imaginar que você precisa construir uma API para uma plataforma financeira que registre um banco de dados de clientes e suas transações. Seu desafio será implementar os métodos do controller CustomerController
e os testes necessários para validar o seu funcionamento.
Parte da aplicação já está configurada, especialmente:
- um banco de dados local em JSON, utilizando a biblioteca
JsonFlatFileDataStore
- Uma classe
CustomerRepository
e uma interfaceICustomerRepository
, que implementam o padrão de projeto repository e que servem para facilitar o acesso à camada de acesso ao banco - a classe
Program
, que já integra os serviços referentes ao banco e ao repository na aplicação, tornando-os acessíveis no controller por meio da injeção de dependência
Como temos um repository já implementado, você pode utilizar os métodos da interface ICustomerRepository
dentro do controller para fazer operações no banco de dados em JSON sem se preocupar com os detalhes de implementação!
Antes de tudo, vamos configurar o projeto:
A classe CustomerController
deve ser decorada com o atributo ApiController
Esse atributo irá permitir que o controller seja detectado como tal pela aplicação.
A classe CustomerController
deve utilizar o atributo Route
para determinar uma rota base "/controller"
A rota definida no atributo Route será usada como base por todos os métodos do controller.
A classe CustomerController
deve herdar da classe ControllerBase
Essa classe define métodos úteis que retornarão objetos derivados de ActionResult e poderão ser usados nos métodos do controller para retornar respostas HTTP completas de forma simples.
A classe CustomerController
deve possuir um campo privado e somente leitura do tipo ICustomerRepository
Esse campo, que armazenará uma referência ao singleton do repositório, poderá ser utilizada para invocar os seus métodos e, através deles, ler e modificar informações armazenadas no banco.
A classe CustomerController
deve possuir um controller que receba um ICustomerRepository
e o atribua ao campo criado anteriormente
O serviço CustomerRepository
será passado para o controller automaticamente, por injeção de dependência, pois trata-se de um dos serviços preconfigurados na classe Program
.
O método GetAll
deve ser acessível por uma requisição GET
na rota "/controller"
Como a rota usada é a mesma que a rota base, apenas é necessário utilizar neste método o atributo que represente o verbo HTTP correto, sem nenhum parâmetro a mais.
O método GetAll
deve responder com um ActionResult
(ou derivado) contendo o status 200 (Ok)
, caso a leitura ocorra corretamente
O método Ok()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
A resposta do método GetAll
deve retornar em seu corpo todas as entradas do banco
Ao usar a rota "GET /controller"
devemos receber em resposta um array JSON contendo todos os objetos do tipo Customer
salvos no banco.
O método GetAllTest
deve conter os testes para o método GetAll
O seu teste deve conter:
- Uma chamada
GET
para a rota"/customers"
utilizando o_client
- Um mock do método
GetAll()
do repositório, configurado usando o_repositoryMock
- Uma verificação de que a resposta retornada pela chamada ao cliente é do tipo
200 (Ok)
- Uma verificação de que o conteúdo da resposta é equivalente ao objeto retornado pelo mock
- Uma verificação de que o método mockado foi chamado uma única vez
O método GetById
deve ser acessível por uma requisição GET
na rota "/controller/{id}"
Como a rota usada possui um query parameter, além do atributo que represente o verbo HTTP correto, é necessário indicar também o parâmetro recebido.
O método GetById
deve responder com um ActionResult
(ou derivado) contendo o status 200 (Ok)
, caso a leitura ocorra corretamente
O método Ok()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método GetById
deve retornar em seu corpo apenas a entrada com o Id
indicado, caso a leitura ocorra corretamente
Ao usar essa rota devemos receber um objeto único em JSON, cujo Id
corresponda ao valor passado na URL.
O método GetById
deve responder com um ActionResult
(ou derivado) contendo o status 404 (Not Found)
, caso não haja um objeto com o Id
passado
O método NotFound()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método GetById
deve retornar em seu corpo a mensagem "Customer not found"
, caso não haja um objeto com o Id
passado
Ao usar essa rota devemos receber apenas uma string
indicando que não há nenhum objeto que possua o Id
correspondente.
O método GetByIdTest
deve conter os testes para o método GetById
O seu teste deve conter:
- Uma chamada
GET
para a rota"/customers/1"
utilizando o_client
- Um mock do método
GetById()
do repositório, configurado usando o_repositoryMock
- O método mockado deve retornar o objeto correto apenas se receber o id
1
- O método mockado deve retornar o objeto correto apenas se receber o id
- Uma verificação de que a resposta retornada pela chamada ao cliente é do tipo
200 (Ok)
- Uma verificação de que o conteúdo da resposta é equivalente ao objeto retornado pelo mock
- Uma verificação de que o método mockado foi chamado uma única vez
O método Create
deve ser acessível por uma requisição POST
na rota "/controller"
Como a rota usada é a mesma que a rota base, apenas é necessário utilizar neste método o atributo que represente o verbo HTTP correto, sem nenhum parâmetro a mais.
O método Create
deve responder com um ActionResult
(ou derivado) contendo o status 201 (Created)
, caso a leitura ocorra corretamente
O método CreatedAtAction()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método Create
deve retornar em seu corpo o objeto criado, contendo o Id
e os timestamps de criação e update atribuídos
Ao usar essa rota, devemos receber um objeto único em JSON, cujo Id
seja único e corretamente atribuído e cujos CreatedAt
e UpdatedAt
sejam criados praticamente no mesmo instante (é tolerável uma diferença de até 100 ms).
O método Create
deve ter um atributo location
no header da resposta com a rota do GetById
que pode acessar o objeto criado
O método CreatedAtAction()
do ControllerBase
retorna esse header automaticamente caso seja configurado da forma correta.
O método CreateTest
deve conter os testes para o método Create
O seu teste deve conter:
- Uma chamada
POST
para a rota"/customers"
utilizando o_client
- Um mock do método
GetNextIdValue()
do repositório, configurado usando o_repositoryMock
- Um mock do método
Create()
do repositório, configurado usando o_repositoryMock
- O método mockado deve retornar o objeto correto apenas se receber um objeto com o mesmo id retornado pelo mock de
GetNextIdValue()
- O método mockado deve retornar o objeto correto apenas se receber um objeto com o mesmo id retornado pelo mock de
- Uma verificação de que a resposta retornada pela chamada ao cliente é do tipo
201 (Created)
- Verificações de que os campos do objeto retornado possuem os valores esperados
- Verificações de que os métodos mockados foram chamados uma única vez cada
O método Update
deve ser acessível por uma requisição PUT
na rota "/controller/{id}"
Como a rota usada possui um query parameter, além do atributo que represente o verbo HTTP correto, é necessário indicar também o parâmetro recebido.
O método Update
deve responder com um ActionResult
(ou derivado) contendo o status 200 (Ok)
, caso a atualização ocorra corretamente
O método Ok()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método Update
deve retornar em seu corpo apenas uma mensagem "Customer {id} updated"
caso a atualização seja bem-sucedida
Ao usar essa rota, devemos receber um objeto único em JSON, cujo Id
corresponda ao valor passado na URL.
O método Update
deve responder com um ActionResult
(ou derivado) contendo o status 404 (Not Found)
, caso não haja um objeto com o Id
passado
O método NotFound()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método Update
deve retornar em seu corpo a mensagem "Customer not found"
, caso não haja um objeto com o Id
passado
Ao usar essa rota devemos receber apenas uma string
indicando que não há nenhum objeto que possua o Id
correspondente.
O método UpdateTest
deve conter os testes para o método Update
O seu teste deve conter:
- Uma chamada
PUT
para a rota"/customers/1"
utilizando o_client
- Um mock do método
Update()
do repositório, configurado usando o_repositoryMock
- O método mockado deve retornar
true
apenas se receber o id1
- O método mockado deve retornar
- Uma verificação de que a resposta retornada pela chamada ao cliente é do tipo
200 (Ok)
- Uma verificação de que o corpo da resposta é a mensagem
"Customer 1 updated"
- Uma verificação de que o método mockado foi chamado uma única vez
O método Delete
deve ser acessível por uma requisição DELETE
na rota "/controller/{id}"
Como a rota usada possui um query parameter, além do atributo que represente o verbo HTTP correto, é necessário indicar também o parâmetro recebido.
O método Delete
deve responder com um ActionResult
(ou derivado) contendo o status 204 (No Content)
, caso a deleção ocorra corretamente
O método NoContent()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método Delete
deve responder com um ActionResult
(ou derivado) contendo o status 404 (Not Found)
, caso não haja um objeto com o Id
passado
O método NotFound()
do ControllerBase
pode ser utilizado para facilitar esse retorno.
O método Delete
deve retornar em seu corpo a mensagem "Customer not found"
, caso não haja um objeto com o Id
passado
Ao usar essa rota, devemos receber apenas uma string
indicando que não há nenhum objeto que possua o Id
correspondente.
O método DeleteTest
deve conter os testes para o método Delete
O seu teste deve conter:
- Uma chamada
DELETE
para a rota"/customers/1"
utilizando o_client
- Um mock do método
Delete()
do repositório, configurado usando o_repositoryMock
- O método mockado deve retornar
true
apenas se receber o id1
- O método mockado deve retornar
- Uma verificação de que a resposta retornada pela chamada ao cliente é do tipo
204 (No Content)
- Uma verificação de que o método mockado foi chamado uma única vez