Neste tutorial vamos apresentar o processo de desenvolvimento com testes utilizando como base uma aplicação Ruby on Rails simples que acessa a Covid19 Brazil API para obter dados de casos da Covid-19 nos estados brasileiros. Utilizaremos Ruby 2.7 e Rails 6.0.
Para acessar os dados da API, precisaremos fazer requisições HTTP e, para lidar com elas, utilizaremos a gem Faraday, uma ferramenta que nos oferece grande praticidade. Você pode ver mais sobre esta gem aqui.
Além disso, vamos usar Rspec puro com stubs para fazer nossos testes. No entanto, vale lembrar que é possível utilizar VCR para melhorar a confiabilidade dos nossos testes, evitando usar stubs, que podem não corresponder ao retorno real da API. Na área de desenvolvimento web existe uma discussão em torno de quando usar stubs ou mocks. Não entraremos nesta discussão aqui, mas você pode ler um pouco sobre isso no artigo do Martin Fowler: Mocks Aren't Stubs.
Vamos começar pelo nosso teste de feature, verificando a apresentação dos dados para cada estado na home:
feature 'Visitor views home page' do
scenario 'successfully' do
data_states = [StateInfo.new(state_id: 11,
uf: "Rondônia",
abbreviation: "RO",
cases: 1,
deaths: 0,
suspicions: 61),
StateInfo.new(state_id: 12,
uf: "Acre",
abbreviation: "AC",
cases: 2,
deaths: 1,
suspicions: 12)]
allow(StateInfo).to receive(:all).and_return(data_states)
visit root_path
expect(page).to have_css('h1', text: "Covid-19 Pandemic Agregator")
expect(page).to have_content('Rondônia')
expect(page).to have_content('RO')
expect(page).to have_content('Casos: 1')
expect(page).to have_content('Mortes: 0')
expect(page).to have_content('Suspeitas: 61')
expect(page).to have_content('Acre')
expect(page).to have_content('AC')
expect(page).to have_content('Casos: 2')
expect(page).to have_content('Mortes: 1')
expect(page).to have_content('Suspeitas: 12')
end
end
Criamos um array com estados – neste caso apenas 2 – e os dados que queremos apresentar. Para simular os dados recebidos da API vamos utilizar um PORO (Plain Old Ruby Object) e fazer um stub para que StateInfo
retorne nosso data_states
quando for chamado o método all
na classe. Com isso, esperamos poder chamar StateInfo.all
e receber um array com todos os dados dos estados e poderemos apresentá-los na home.
Nossos testes unitários vão garantir que o método all
está recebendo os dados no formato enviado pela API e retornando um array no formato que esperamos. Além disso, aproveitamos para lidar com o possível erro da API não responder corretamente:
describe StateInfo do
it 'should get all states information' do
json = File.read(Rails.root.join('spec/support/brazil_states_data.json'))
url = 'https://covid19-brazil-api.now.sh/api/report/v1'
response = double('faraday_response', body: json, status: 200)
allow(Faraday).to receive(:get).with(url).and_return(response)
result = StateInfo.all
expect(result.length).to eq 3
expect(result[0].uf).to eq 'Rondônia'
expect(result[0].abbreviation).to eq 'RO'
expect(result[0].cases).to eq 0
expect(result[0].deaths).to eq 0
expect(result[0].suspicions).to eq 61
expect(result[1].uf).to eq 'Acre'
expect(result[2].uf).to eq 'Amazonas'
end
it 'should return return empty array if API error' do
url = 'https://covid19-brazil-api.now.sh/api/report/v1'
response = double('faraday_response', body: "{}", status: 500)
allow(Faraday).to receive(:get).with(url).and_return(response)
result = StateInfo.all
expect(result.length).to eq 0
end
end
Para simular os dados da API vamos novamente fazer um stub. Note que utilizaremos o Faraday para montar nossa resposta da requisição HTTP, por isso o teste fica assim: allow(Faraday).to receive(:get).with(url).and_return(echo)
. Além disso, utilizamos um arquivo de texto externo para diminuir a quantidade de linhas do teste. Assim, criamos o arquivo brazil_states_data.json
com o seguinte:
{
"data": [
{
"uid": 11,
"uf": "Rondônia",
"state": "RO",
"cases": 0,
"deaths": 0,
"suspects": 61,
"refuses": 2,
"broadcast": false,
"comments": "",
"datetime": "2020-03-18T23:00:00.000Z"
},
{
"uid": 12,
"uf": "Acre",
"state": "AC",
"cases": 0,
"deaths": 0,
"suspects": 12,
"refuses": 0,
"broadcast": false,
"comments": "",
"datetime": "2020-03-18T23:00:00.000Z"
},
{
"uid": 13,
"uf": "Amazonas",
"state": "AM",
"cases": 1,
"deaths": 0,
"suspects": 18,
"refuses": 26,
"broadcast": false,
"comments": "",
"datetime": "2020-03-18T23:00:00.000Z"
}
]
}
Depois disso, implementamos nossa classe StateInfo
com os métodos initialize
e all
:
class StateInfo
attr_accessor :state_id, :uf, :abbreviation, :cases, :deaths, :suspicions
def initialize(state_id:, uf:, abbreviation:, cases:, deaths:, suspicions:)
@state_id = state_id
@uf = uf
@abbreviation = abbreviation
@cases = cases
@deaths = deaths
@suspicions = suspicions
end
def self.all
response = Faraday.get('https://covid19-brazil-api.now.sh/api/report/v1')
return {} unless response.status == 200
json = JSON.parse(response.body, symbolize_names: true)[:data]
result = json.map do |state|
state = new(state_id: state[:uid],uf: state[:state], abbreviation: state[:uf],
cases: state[:cases], deaths: state[:deaths],
suspicions: state[:suspects])
end
result
end
end
No initialize
podemos incluir os parâmetros que acharmos mais importantes. Agora sim, dentro do método all
, usamos Faraday.get
para criar a resposta que queremos a partir da requisição usando o endereço da API: 'https://covid19-brazil-api.now.sh/api/report/v1'
.
Nesse instante, podemos receber a resposta e tratar os dados, utilizando-os para montar um array com os dados de cada estado. Ao final, o método all
retornará esse array com as instâncias de StateInfo
, que será utilizado para exibir as informações na view.
Esse é apenas um exemplo simplificado – do teste até a implementação – de como você pode usar o Faraday para tratar requisições HTTP em uma aplicação Ruby on Rails. Essa ferramenta possui muitas outras funções. Consulte a documentação oficial da gem para aprender como utilizá-las para as situações específicas da sua aplicação.