Artigo original: How to Build an SPA with Vue.js and C# Using .NET Core

Digamos que você é um desenvolvedor de front-end ou que tenha trabalhado mais com o front-end recentemente.

Às vezes, você acaba usando algumas tecnologias de back-end, mas tende a ficar na sua zona de conforto, provavelmente no universo do JavaScript. É possível que você tenha criado uma pequena API com Python em algum momento também.

Você, no entanto, nunca tocou na stack de tecnologias moderna da família .NET.

Nesse sentido, este tutorial vai guiá-lo, passo a passo, na criação de uma aplicação moderna de Single-Page Application (SPA), que usará o Vue.js para o front-end e o .NET Core (C#) para o back-end.

Além disso, veremos como escrever alguns testes, tanto de unidade quanto de integração, a fim de cobrir a funcionalidade do front-end e do back-end (ao menos, parcialmente).

Se você quiser pular a leitura, aqui está o repositório do GitHub com um README detalhado.

O que vamos criar?

Vamos criar uma aplicação da web na qual os usuários podem fazer login/se cadastrar e nos dizer o quanto eles gostam de pizza ao pressionar um botão dizendo "I love it" ('Eu adoro pizza').

Não há restrições de quantas vezes cada usuário pode nos mostrar o quanto gosta de pizza. O único requisito é que apenas usuários logados podem votar.

Na página inicial, junto com os botões de login/cadastro, colocaremos um pequeno gráfico de barras, exibindo os X usuários com a maior apreciação (no eixo X) e o número de votos (no eixo Y)."

Instalação

Vamos começar pelo front-end. Faz mais sentido construir primeiro as partes visíveis da nossa aplicação. Assim, construiremos o nosso front-end com uma das bibliotecas de front-end mais famosas do mercado: o Vue.js.

Como configurar o front-end

Para instalar e configurar inicialmente o nosso front-end com Vue.js, vamos usar a CLI do Vue. Trata-se de uma ferramenta de linha de comando padrão para o desenvolvimento de aplicativos em Vue.js.

Nota da tradução: CLI é a abreviação para Command Line Interface, ou interface da linha de comando, em português.

Para instalá-la, use o comando abaixo. Observe que todos os comandos neste tutorial usam o NPM, que você precisa ter instalado na sua máquina para acompanhar.

npm install -g @vue/cli
Instalando a CLI do Vue

Depois de instalar a CLI do Vue com sucesso, poderemos utilizá-la para instalar e configurar o Vue.js. Vamos fazer isso por meio deste processo:

1º Passo: crie um diretório de projeto vazio e abra-o com os seguintes abaixo.

mkdir pizza-app
cd pizza-app
Criando um novo diretório de projeto vazio

Passo 2: dentro do diretório raiz do projeto, execute o comando a seguir.

vue create frontend
Criando a parte de front-end do app

Em seguida, nas opções fornecidas, selecione o seguinte:

  • Seleção manual de recursos "Manually select features".
image-52
Instalação do Vue.js – seleção manual dos recursos
  • Babel
  • Router
  • Pré-processadores do CSS (CSS-Preprocessors, como o SASS)
image-53
Instalação do Vue.js – seleção dos recursos

Em seguida, selecione a versão 2.x na próxima tela:

image-54
Selecionando a versão 2.x
Nota de tradução: o Vue.js, no momento da tradução, está em sua versão 3 e o suporte à versão 2 terminará ao final deste ano. Neste momento, no entanto, ainda é possível realizar as configurações de acordo com este tutorial seguindo as instruções.

Em seguida, selecione  'Use history mode for router?' e 'Sass/SCSS (with node-sass)', assim:

image-57
  • Linter / Formatter
image-58
  • Teste de unidade com Jest
image-59
  • Teste de ponta a ponta (E2E) com Cypress
image-60

Depois desse último passo, finalize o processo de instalação com as opções padrão.

Agora estamos prontos para executar a aplicação usando os seguintes comandos a partir da pasta raiz do projeto:

cd frontend
npm run serve

Depois que o app estiver em execução, você poderá vê-lo no seu navegador em http://localhost:8080:

welcome-to-your-vuejs-app
Tela de instalação padrão do Vue.js

Antes de prosseguir com a criação dos componentes reais que a nossa aplicação de front-end terá, vamos instalar a parte do back-end da nossa aplicação por meio da CLI do .NET Core.

Como configurar o back-end

Como mencionado acima, usaremos outra ferramenta de linha de comando, a CLI do .NET Core, que possibilita a criação e configuração de apps .NET.

Se você ainda não tem, pode usar este link para baixá-la e, em seguida, instalá-la.

Depois de ter a CLI do .NET Core instalada, vá para o diretório raiz do seu projeto e execute o seguinte comando para criar o back-end da nossa aplicação. Criaremos uma pasta chamada backend e instalaremos um app .NET para a web dentro dela:

dotnet new web -o backend

gitignore.io

Antes de continuar com a instalação dos próximos pacotes que precisaremos, vamos organizar nosso arquivo .gitignore.

Este é um arquivo de configuração que informa ao Git o que ignorar ao fazer o commit das alterações em repositórios baseados em Git (os do GitHub).

Como queremos ter um arquivo .gitignore, ele incluirá regras para dois tipos de aplicações:

  • uma baseada em Node.js, que é o nosso front-end do Vue.js; e
  • uma para o .NET (C#) que é o nosso back-end.

Para isso, usaremos uma ferramenta chamada gitignore.io. Essa ferramenta vai gerar esses arquivos para nós. A vantagem de usar essa ferramenta é que ela nos permite digitar quais são nossas linguagens de programação/plataformas e gera o arquivo .gitignore para nós.

image-61
Usando o gitignore.io para gerar um arquivo .gitignore

Além disso, no topo do arquivo gerado, ele salva links para criação ou edição posterior, como mostrado abaixo:

# Created by https://www.toptal.com/developers/gitignore/api/webstorm,vue,vuejs,visualstudio
# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm,vue,vuejs,visualstudio

Agora, estamos prontos para instalar o restante dos pacotes que precisamos.

Primeiro, vamos instalar um pacote chamado SpaServices, que nos permitirá ter a aplicação em execução usando apenas um URL e apontando para a aplicação .NET. De sua parte, ele encaminhará solicitações para a nossa aplicação de front-end.

Para instalá-lo, abra o seu terminal, vá para a pasta backend do seu projeto e digite o seguinte comando:

dotnet add package Microsoft.AspNetCore.SpaServices.Extensions --version 3.1.8

Depois disso, precisamos configurar a nossa aplicação de back-end com o pacote SpaServices para ter o resultado desejado.

Todas as solicitações serão encaminhadas para a nossa aplicação de back-end .NET e, se a solicitação for destinada ao front-end, ela será encaminhada.

Para fazer isso, abra o arquivo Startup.cs e atualize seu conteúdo para ficar assim:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace backend
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // Este método é chamado pelo tempo de execução. Use-o para adicionar serviços ao contêiner.
        // Para obter mais informações sobre como configurar sua aplicação, acesse https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            string connectionString = Configuration.GetConnectionString("DefaultConnection");
            services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite(connectionString));
            services.AddSpaStaticFiles(configuration: options => { options.RootPath = "wwwroot"; });
            services.AddControllers();
            services.AddCors(options =>
            {
                options.AddPolicy("VueCorsPolicy", builder =>
                {
                    builder
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials()
                    .WithOrigins("https://localhost:5001");
                });
            });
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(options =>
                {
                    options.Authority = Configuration["Okta:Authority"];
                    options.Audience = "api://default";
                });
            services.AddMvc(option => option.EnableEndpointRouting = false);
        }

        // Este método é chamado pelo tempo de execução. Use-o para configurar o pipeline da solicitação HTTP.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext dbContext)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseCors("VueCorsPolicy");

            dbContext.Database.EnsureCreated();
            app.UseAuthentication();
            app.UseMvc();
            app.UseRouting();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
            app.UseSpaStaticFiles();
            app.UseSpa(configuration: builder =>
            {
                if (env.IsDevelopment())
                {
                    builder.UseProxyToSpaDevelopmentServer("http://localhost:8080");
                }
            });
        }
    }
}

Essa é a versão final do arquivo Startup.cs e, por isso, você provavelmente deve ter notado algumas outras coisas nela. Voltaremos nisso um pouco mais tarde neste tutorial.

Neste ponto, você deve ser capaz de executar a sua aplicação back-end. Se quiser fazer isso, execute os seguintes comandos a partir da pasta raiz do seu projeto:

cd backend
dotnet run

Como configurar a autenticação

Como você deve se lembrar da descrição no início, nossa aplicação deve ter uma opção de registro/login.

Para atender a esse requisito, usaremos um serviço terceirizado chamado Okta. Instalaremos os pacotes necessários para usar o Okta SDK no front-end e no back-end do nosso app.

Antes disso, porém, você precisa criar uma conta no site deles e obter acesso ao painel de administração. A partir daí, você pode criar uma aplicação. Aqui vemos como fica na minha aplicação:

image-62

Agora, vamos começar com o pacote que precisamos para nosso front-end. Na pasta raiz, execute os seguintes comandos:

cd frontend
npm i @okta/okta-vue

Após esta etapa, estamos prontos para alterar nossa aplicação do Vue.js para que use as vantagens do Okta SDK.

Também instalaremos outro pacote, chamado BootstrapVue, que fornece um conjunto de componentes do Vue.js bonitos e prontos para uso. Isso nos ajudará a economizar tempo de desenvolvimento e também nos dará um front-end de boa aparência.

Para instalá-lo, execute o seguinte na sua pasta frontend:

npm i vue bootstrap-vue bootstrap

Como configurar o roteador

Chegou a hora de escrever um pouco de código. Precisamos editar nosso roteador para aplicar o que vem dos serviços de autenticação do Okta.

Você pode ver o arquivo completo no meu repositório do GitHub, mas aqui está a parte essencial que você precisa configurar com seus próprios dados (que você obteve quando se registrou no site do desenvolvedor do Okta):

Vue.use(Auth, {
  issuer: 'https://dev-914982.okta.com/oauth2/default',
  client_id: '0oatq53f87ByM08MQ4x6',
  redirect_uri: 'https://localhost:5001/implicit/callback',
  scope: 'openid profile email'
})

....

router.beforeEach(Vue.prototype.$auth.authRedirectGuard())

Tela inicial

Depois de classificar nosso roteador, podemos finalmente fazer algumas alterações na tela inicial da nossa aplicação, que ficará visível para nossos usuários.

Abra o arquivo App.vue em seu IDE e altere seu conteúdo da seguinte maneira:

<template>
  <div id="app">
    <header>
      <b-navbar toggleable="md" type="light" variant="light">
        <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
        <b-navbar-brand to="/">Love Pizza</b-navbar-brand>
        <b-collapse is-nav id="nav-collapse">
          <b-navbar-nav>
            <b-nav-item href="#" @click.prevent="login" v-if="!user">Login</b-nav-item>
            <b-nav-item href="#" @click.prevent="logout" v-else>Logout</b-nav-item>
          </b-navbar-nav>
        </b-collapse>
      </b-navbar>
    </header>
    <main>
      <div>
        Love pizza button and clicks counter here
      </div>
    </main>
  </div>
</template>

<script>
export default {
  name: 'app',
  data() {
    return {
      user: null
    }
  },
  async created() {
    await this.refreshUser()
  },
  watch: {
    '$route': 'onRouteChange'
  },
  methods: {
    login() {
      this.$auth.loginRedirect()
    },
    async onRouteChange() {
      await this.refreshUser()
    },
    async refreshUser() {
      this.user = await this.$auth.getUser()
    },
    async logout() {
      await this.$auth.logout()
      await this.refreshUser()
      this.$router.push('/')
    }
  }
}
</script>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

Neste momento, sua aplicação deve ter a seguinte aparência:

image-173
Front-end do app em Vue.js – em desenvolvimento

Observação: não se confunda com o texto 'Love pizza button and clicks counter here' (aqui vão o botão de pizza e o contador de cliques). Ao criar as UIs, é uma boa prática deixar esses espaços reservados para componentes que serão desenvolvidos no futuro.

No nosso caso, é aqui que adicionaremos os componentes responsáveis pelo botão 'I love it' e para o contador posteriormente. Eles mostrarão o número de votos por usuário.

<main>
  <div>
  	Love pizza button and clicks counter here
  </div>
</main>
Espaço reservado para componentes a serem adicionados posteriormente

Autenticação no back-end

Até agora, configuramos nosso front-end para que use o serviço de autenticação fornecido pelo Okta. Porém, para ter toda a imagem pronta para uso, precisamos fazer o mesmo no lado do back-end.

É de onde faremos chamadas HTTP, para nos comunicarmos com o banco de dados. Como você verá mais adiante, algumas dessas chamadas precisarão ser autenticadas para serem bem-sucedidas.

Vamos começar novamente com a instalação de alguns pacotes que facilitarão nosso trabalho. No seu terminal, vá para o diretório backend e execute o seguinte:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.1.8
dotnet add package Microsoft.Extensions.Configuration --version 3.1.7

Então, precisamos de outro pacote que nos ajude a configurar nosso banco de dados. Usaremos o banco de dados SQLite, que é superfácil de usar, na configuração do .NET Core.

Ainda na pasta backend, execute:

dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 2.1.1

Assim que terminarmos as instalações, certifique-se de que tem o conteúdo completo do arquivo Startup.cs (e se ainda não o fez, recomendo fazer isso agora).

PizzaVotesApiService

OK, pessoal!  Configuramos nosso front-end e nosso back-end para oferecer suporte à autenticação. Adicionamos o SQLite como um banco de dados a ser usado para armazenar os votos dos usuários. Por fim, temos uma versão inicial da nossa tela inicial.

Agora, é hora de implementar um serviço no front-end que nos permitirá consumir a API do nosso back-end.

Ótimo trabalho até agora!

Antes de poder fazer chamadas HTTP do front-end para o back-end, precisamos instalar mais um pacote em nossa aplicação do Vue.js. Esse pacote se chama axios e nos dá a capacidade de fazer XMLHttpRequests a partir do navegador, que é exatamente o que precisamos.

Abra seu terminal, vá para a pasta frontend do seu projeto e execute:

npm i axios

Em seguida, em seu IDE, vá para a pasta src do front-end do seu app, crie um arquivo .js e adicione o seguinte código dentro dele:

import Vue from 'vue'
import axios from 'axios'

const client = axios.create({
    baseURL: 'http://localhost:5000/api/pizzavotes',
    json: true
})

export default {
    async execute(method, resource, data) {
        const accessToken = await Vue.prototype.$auth.getAccessToken()
        return client({
            method,
            url: resource,
            data,
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        }).then(req => {
            return req.data
        })
    },
    getAll() {
        return this.execute('get', '/')
    },
    getById(id) {
        return this.execute('get', `/${id}`)
    },
    create(data) {
        return this.execute('post', '/', data)
    },
    update(id, data) {
        return this.execute('put', `/${id}`, data)
    },
}

Eu dei nome ao meu arquivo de PizzaVotesApiService.js. Pararemos com a integração da API por um tempo e criaremos outro componente que conterá os controles que os usuários usarão para interagir com essa API.

Componente do painel

Diga "olá" para o nosso componente Dashboard.vue.

É aqui que colocaremos o botão 'I love it' ('Eu adoro pizza') e um pequeno contador de votos.

image-63
Botão 'I love it' e contador de votos

Também adicionaremos uma imagem de pizza divertida.

image-64
Imagem da pizza

Também adicionaremos um belo gráfico de barras, mostrando os X principais eleitores.

image-65
Gráfico de barras dos principais votantes

Você pode copiar e colar o conteúdo completo do arquivo do meu repositório para que possamos continuar integrando tudo.

Integração da API

Vou usar um pequeno diagrama para representar o fluxo de dados. Como dizem, "uma imagem vale mais que mil palavras":

data-flow
Diagrama do fluxo de dados

Como você pode ver no diagrama (eu espero), quando o usuário insere seus votos, os dados vão do componente do painel para o serviço de API que criamos para comunicação com a API de back-end. Em seguida, ele alcança o controlador de back-end que está de fato fazendo as chamadas HTTP.

Depois que os dados são buscados, o serviço os repassa para nossa interface do usuário, onde os mostramos por meio de nossos componentes do Vue.js. Como você verá, existe alguma lógica adicional que verifica se o usuário está autenticado para saber quais chamadas executar.

Aqui está a implementação do controller em si:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace backend.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PizzaVotesController : ControllerBase
    {
        private readonly ApplicationDbContext _dbContext;

        public PizzaVotesController(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        // GET api/pizzavotes
        [HttpGet]
        public async Task<ActionResult<List<PizzaVotes>>> Get()
        {
            return await _dbContext.PizzaVotes.ToListAsync();
        }

        // GET api/pizzavotes/{email}
        [Authorize]
        [HttpGet("{id}")]
        public async Task<ActionResult<PizzaVotes>> Get(string id)
        {
            return await _dbContext.PizzaVotes.FindAsync(id);
        }

        // POST api/pizzavotes
        [Authorize]
        [HttpPost]
        public async Task Post(PizzaVotes model)
        {
            await _dbContext.AddAsync(model);
            await _dbContext.SaveChangesAsync();
        }

        // PUT api/pizzavotes/{email}
        [Authorize]
        [HttpPut("{id}")]
        public async Task<ActionResult> Put(string id, PizzaVotes model)
        {
            var exists = await _dbContext.PizzaVotes.AnyAsync(f => f.Id == id);
            if (!exists)
            {
                return NotFound();
            }

            _dbContext.PizzaVotes.Update(model);

            await _dbContext.SaveChangesAsync();

            return Ok();
        }
    }
}

Aqui temos quatro métodos para executar as quatro operações básicas:

  • Obter todos os registros do banco de dados;
  • Obter os registros de um usuário (pelo endereço de e-mail);
  • Criar um novo registro de usuário;
  • Atualizar os registros de um usuário existente.

Você provavelmente notou a cláusula [Authorize] acima de três dos quatro métodos. Esses métodos vão exigir que o usuário seja autenticado para executar.

Deixaremos o método GET api/pizzavotes sem autorização para obter todos os registros de propósito. Como gostaríamos de mostrar o gráfico de barras na tela inicial para todos os usuários, precisaremos poder fazer esta chamada, independentemente de o usuário estar autenticado ou não.

Permitindo o registro

Algo para se observar: se você gostaria de ter um 'Cadastre-se' em sua tela de login, você precisa permitir isso no painel de administração do Okta.

Uma vez logado, selecione no menu Users -> Registration (Usuários -> Registro):

image-66
Permitir registro de novos usuários

Finalizando o back-end

Para que o back-end de sua aplicação funcione totalmente, dê uma olhada no meu repositório aqui e adicione os arquivos ausentes.

Se você acompanhou até este ponto, você deve ter os seguintes arquivos (exceto a pasta de teste, pois vamos adicioná-la mais a frente):

image-67
Estrutura do back-end do app

Finalizando o front-end

Para finalizar o trabalho do front-end da nossa aplicação, criaremos mais dois componentes.

O primeiro renderizará o gráfico de barras mencionado acima, enquanto o segundo exibirá o endereço de e-mail do usuário que está conectado no momento.

No seu IDE, vá para frontend/src/components e crie dois arquivos, chamados Username.vue e VotesChart.vue, respectivamente.

Username.vue é um componente muito curto e simples que pega o endereço de e-mail do usuário como suporte e o renderiza. Aqui está a sua implementação:

<template>
  <div class="username">{{ username }}</div>
</template>

<script>
export default {
  props: { username: String },
}
</script>

<style lang="scss">
.username {
  color: rebeccapurple;
  display: flex;
  align-items: center;
  justify-content: center;
}

.username::before {
  content: "•";
}

.username::after {
  content: "•";
}
</style>

Um único ponto que é preciso observar aqui é que estamos usando SASS/SCSS para definir os estilos dos componentes. Isso é possível por termos escolhido essa opção no início (quando estávamos instalando nossa aplicação do Vue.js).

Para desenhar o gráfico, usaremos um pacote chamado vue-chart.js. Instale-o executando o seguinte comando na sua pasta frontend:

npm i vue-chartjs chart.js

Nosso componente VotesChart.vue será uma espécie de wrapper para o componente gráfico de barras que vem do pacote vue-chartjs.


Nós o usamos para obter os dados do componente pai, Dashboard.vue, e processá-los. Classificamos o array de dados para exibir nossos principais votantes, classificados do maior para o menor número de votos.

Em seguida, passamos para o componente do gráfico de barras para visualizá-lo. Aqui está a implementação completa:

<script>
import { Bar } from 'vue-chartjs'

const TOP_N_VOTERS_TO_SHOW_ON_CHART = 10

// Usado para ordenar pelo valor dos votos - do maior para o menor (desc)
function sortByStartDate(nextUser, currentUser) {
  const nextVoteValue = nextUser.value
  const currentVoteValue = currentUser.value
  return currentVoteValue - nextVoteValue
}

export default {
  extends: Bar,
  props: { data: Array },

  watch: {
    data: async function (newVal) {
      const sortedVotes = Array.from(newVal).sort(sortByStartDate).slice(0, TOP_N_VOTERS_TO_SHOW_ON_CHART)
      this.labels = sortedVotes.map(user => user.id)
      this.values = sortedVotes.map(user => user.value)

      this.renderChart({
        labels: this.labels,
        datasets: [
          {
            label: 'Pizza Lovers',
            backgroundColor: '#f87979',
            data: this.values,
          }
        ]
      }, {
        scales: {
          yAxes: [{
            ticks: {
              stepSize: 1,
              min: 0,
              max: this.values[0]
            }
          }]
        }
      })
    }
  }
}
</script>

Observe que há uma constante na parte superior do arquivo que definirá quantos dos principais votantes gostaríamos de mostrar neste gráfico. Atualmente, está definida como 10, mas você pode alterá-la como quiser.


Quando estiver pronto com todo o front-end e quiser executar a aplicação, você pode fazer isso:

Vá para a pasta frontend e execute:

npm run serve

Vá para a pasta backend e execute:

dotnet run

Abra seu navegador e acesse https://localhost:5001.

Como testar nossas aplicações

Até agora, criamos uma aplicação de página única moderna e totalmente funcional com um back-end em .NET Core e um banco de dados SQLite. Isso é muito trabalho. Parabéns! ✨

Facilmente, poderíamos parar aqui e tomar um suco ou um café.

Poré,...

Somos razoáveis o suficiente para saber que, se não testarmos nossas aplicações, não podemos garantir que funcionarão conforme o esperado.

Também sabemos que, se quisermos tornar nosso código testável, devemos escrevê-lo de maneira bem estruturada, tendo em mente os princípios básicos do design de software (texto em inglês).

Espero ter convencido você a continuar trabalhando neste tutorial. Afinal, a única coisa que nos resta fazer é escrever alguns testes para nossas duas aplicações. Então, vamos fazê-lo!

Trataremos do funcionamento de nossa API de back-end com testes de integração e, em nosso front-end, escreveremos testes de unidade e integração.

Testes de unidade e de integração

Antes de passar para o código, gostaria de dizer algumas palavras sobre esses tipos de testes e responder a algumas das perguntas mais frequentes.

Você deve estar se perguntando, o que são testes de unidade? O que são testes de integração? Por que nós precisamos deles? Quando devemos usar cada um deles?

Partindo da primeira pergunta, um teste de unidade é um trecho de código que testa a funcionalidade de um módulo encapsulado (ou seja, outro trecho). É escrito como uma função ou algum tipo de bloco de código independente.

Eles são bons de se ter, porque fornecem um feedback rápido do tempo de desenvolvimento e mantêm o código protegido contra regressões quando novos recursos são adicionados.

Os testes de integração são úteis quando precisamos testar como vários módulos/unidades estão trabalhando juntos. Por exemplo, uma API REST e uma interação com um banco de dados.

Dependendo do tamanho da aplicação em que estamos trabalhando, podemos precisar apenas de testes de integração. Às vezes, no entanto, precisamos de testes de integração e de unidade para testar de verdade o nosso código.

Idealmente, deveríamos ter ambos, pois são partes essenciais da pirâmide de testes e são os mais baratos de implementar.

Em alguns casos, contudo, como para nosso back-end, apenas testes de integração são necessários para cobrir a funcionalidade que vale a pena ser testada. Temos apenas os métodos de API para trabalhar com o banco de dados.

Como criar nossos testes de back-end

Desta vez, começaremos criando nossa solução de teste. Para fazer isso, você precisa fazer algumas coisas.

Primeiro, instale a biblioteca xUnit executando o seguinte no diretório raiz do projeto:

dotnet add package xUnit

Em seguida, vá para a pasta de backend e crie e esvazie a pasta chamada tests. Então, dentro dessa pasta, execute:

dotnet new xunit -o PizzaVotes.Tests

Feito isso, abra backend.csproj e adicione as duas linhas a seguir ao bloco <PropertyGroup>:

<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>

Em seguida, vá para a pasta tests e instale os seguintes pacotes:

Microsoft.AspNetCore.Mvc
Microsoft.AspNetCore.Mvc.Core
Microsoft.AspNetCore.Diagnostics
Microsoft.AspNetCore.TestHost
Microsoft.Extensions.Configuration.Json
Microsoft.AspNetCore.Mvc.Testing

Você faz isso executando cada um dos seguintes comandos em seu terminal:

dotnet add package Microsoft.AspNetCore.Mvc --version 2.2.0
dotnet add package Microsoft.AspNetCore.Mvc.Core --version 2.2.5
dotnet add package Microsoft.AspNetCore.Diagnostics --version 2.2.0
dotnet add package Microsoft.AspNetCore.TestHost --version 3.1.8
dotnet add package Microsoft.AspNetCore.Mvc.Testing --version 3.1.8

Depois de ter tudo instalado, estamos prontos para começar a escrever alguns testes.

Como você pode ver aqui, excetuando os próprios testes, adicionei mais dois arquivos que precisamos ou que são bons de se ter ao executar os testes.

Um deles é apenas um arquivo auxiliar com um método para lidar com objetos serializados e obter o conteúdo da string. O outro é o arquivo fixture, onde temos as configurações e definições do nosso servidor de teste e o client.

Claro, há também um arquivo com nossos testes.

Não vou colar o conteúdo desses arquivos aqui, pois este tutorial já ficou longo o suficiente.

Você pode simplesmente copiá-los do meu repositório.

Se você observar os testes mais de perto, poderá perceber que estamos testando apenas a primeira chamada, não autenticada, para uma resposta de sucesso. Para o restante, estamos verificando apenas a resposta HTTP 401, Unauthorized (não autorizada).

Isso ocorre porque apenas o primeiro método é público, ou seja, não precisa de autenticação.

Se tivéssemos os mesmos testes para todos os métodos, precisaríamos implementar um middleware apenas para autorizar nossa aplicação de teste na frente dos serviços de autenticação da Okta.

Como o objetivo deste tutorial é aprender uma variedade de coisas, podemos dizer que não vale a pena.

Agora a parte divertida: como executar os testes. Acontece que é muito simples. Basta ir ao seu diretório tests (onde está o arquivo tests.sln) do seu terminal e executar:  

dotnet test

Você deve ver algo assim em seu terminal (ignore os avisos amarelos):

image-68
Executando os testes do back-end

Como criar nossos testes para o front-end

É hora de adicionar alguns testes ao nosso front-end. Aqui, faremos testes de unidade e de integração.

Testes de unidade
Como mencionei acima, os testes de unidade são adequados quando temos um módulo ou componente que não possui dependências do mundo externo.

Esses componentes acabam sendo nossos componentes Username.vue e VotesChart.vue.

Eles são componentes representacionais, que recebem os dados de que precisam para funcionar adequadamente por meio de props. Isso significa que podemos escrever nossos testes da mesma maneira: passar a eles os dados de que precisam e verificar se os resultados de sua execução são os esperados.

Aqui, há algo importante para se destacar. Isso não significa que aquilo que foi fornecido pelo pacote @vue/test-utils (que vem ao instalar o Vue.js) não tenha sido o suficiente para testar ambos os componentes.

Em vez disso, para fins educacionais, decidi instalar e usar a biblioteca de testes do Vue também. É por isso que um dos componentes abaixo é testado com @vue/test-utils, mas o outro é testado com @testing-library/vue.

Não se esqueça de instalá-la antes de executar o teste:

npm i --save-dev @testing-library/vue

Novamente, para economizar espaço, não vou colar o código do teste do componente aqui, mas você pode vê-lo facilmente aqui e aqui.

Então, para executar os testes de unidade do seu front-end, vá para a pasta frontend e execute:

npm run test:unit

Teste de integração
Esse tema é provavelmente bem interessante para alguns de vocês.

Se você se lembra do início deste tutorial, quando instalamos nossa aplicação do Vue.js, para nossa solução de testes e2e (ou de integração), selecionamos o Cypress.js.

Esta é uma ferramenta superfácil de usar, que permite aos desenvolvedores escrever testes e2e (end-to-end, ou de ponta a ponta) reais para suas aplicações, fornecendo feedback imediato.

Por experiência pessoal, eu diria que trabalhar com o Cypress é mais prazeroso do que com outros frameworks semelhantes. Se você tiver experiência anterior com estruturas como Nightwatch.js ou Selenium, o que você vê abaixo pode ser familiar para você.

Antes de executar nossos testes com o Cypress, precisamos fazer algumas pequenas alterações em sua configuração.

Encontre o arquivo de índice plugins e adicione a seguinte linha à declaração de retorno no final do arquivo:

  baseUrl: "https://localhost:5001"

Agora, atualize o conteúdo do test.js na pasta specs conforme mostrado aqui.

Depois de fazer tudo, você poderá executar seus testes e2e via Cypress. Você pode fazer isso executando o seguinte comando enquanto estiver no diretório do frontend:

npm run test:e2e

⚡Não se esqueça de iniciar sua aplicação de back-end antes de executar os testes e2e para que funcionem corretamente.

Se você fez tudo como mostramos, depois de executar o comando acima, você deve ver algo assim em seu terminal:

image-69
Executando testes e2e

Uma nova janela do navegador será aberta pelo próprio Cypress.js, onde você pode usar o URI fornecido para ver e executar seus testes.

image-70
UI do Cypress.js

Quando todos os testes forem aprovados, você deverá ver uma tela como esta:

image-71
Os testes e2e passaram com sucesso

Conclusão

Neste tutorial, vimos como usar duas das tecnologias mais importantes do mercado para criar um front-end e um back-end.

Também aprendemos como combiná-las adequadamente para criar uma aplicação de página única pequena, mas totalmente funcional, com suporte a banco de dados.

Por fim, também escrevemos testes de unidade e de integração para ambas as pontas do projeto.

Acredito que esse tipo de exercício seja benéfico tanto para leitores experientes quanto para iniciantes, pois aborda muitas coisas diferentes passo a passo. No final, se tiver concluído todo o processo, você tem uma aplicação funcional de exemplo.

Este tutorial acabou sendo muito mais longo do que eu inicialmente pensei que seria. Se, contudo, você já fez tudo, eu admiro você! Espero que tenha sido um prazer para você ler o artigo, assim como foi para mim escrevê-lo.

Obrigado pela leitura!

Recursos

Você pode encontrar abaixo os links que foram úteis para mim, de algum modo, enquanto escrevia o artigo (recursos em inglês).

https://consultwithgriff.com/spas-with-vuejs-aspnetcore/
https://github.com/okta/okta-auth-dotnet
https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-dotnet-test
https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage?view=netcore-3.1
https://vue-test-utils.vuejs.org/guides/#testing-key-mouse-and-other-dom-events
https://docs.cypress.io/guides/references/configuration.html#Options
https://docs.cypress.io/guides/tooling/visual-testing.html#Functional-vs-visual-testing
https://www.codingame.com/playgrounds/35462/creating-web-api-in-asp-net-core-2-0/part-3---integration-tests
https://testing-library.com/docs/vue-testing-library/intro
https://www.valentinog.com/blog/canvas/