Para testar as models associadas ao Devise, vamos usar como exemplo a model User:
rails g devise User name:string
Lembre-se de fazer a migração:
rails db:migrate
Se você fez a instalação do rspec e do factory-bot corretamente como descrita nos passos Instalação do rspec e Instalação do Factory Bot, então na pasta rspec/factories deve ter sido criado o arquivo users.rb e na pasta rspec/models criado o arquivo user_spec.rb.
Com isso, fazemos como nas outras models, com o uso do factory bot, para criar um default User:
Observe que precisamos colocar um email e senha que são obrigatórias para uma model do Devise.
Feito isso, podemos começar a testar a model no user_spec.rb como foi feito anteriormente:
RSpec.describeUser,type::modeldo describe 'factory'do context 'when using standard factory'do it { expect(build(:user)).to be_valid }endend describe 'validations'do context 'when user does not have an email'do it { expect(build(:user,email:nil)).not_to be_valid }endendend
A partir disso, podemos adicionar outras validações que dependerão das validations da model User(no arquivo app/models/user.rb), como presence do name:
validates :name, presence: true
Dessa forma:
RSpec.describeUser,type::modeldo describe 'factory'do context 'when using standard factory'do it { expect(build(:user)).to be_valid }endend describe 'validations'do context 'when user does not have an email'do it { expect(build(:user,email:nil)).not_to be_valid }end context 'when user does not have a name'do it { expect(build(:user,name:nil)).not_to be_valid }endendend
Testando controllers que exigem autenticação
Para testarmos controllers, será necessário adicionar no arquivo rails_helper.rb criado automaticamente na própria pasta rspec as seguintes linhas dentro do bloco RSpec.configure do |config|:
# Devise Test helpers config.includeDevise::Test::ControllerHelpers,type::controller config.includeDevise::Test::IntegrationHelpers,type::request
Assim, o bloco RSpec.configure do |config| deve ficar mais ou menos assim:
RSpec.configuredo|config|# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path="#{::Rails.root}/spec/fixtures"# If you're not using ActiveRecord, or you'd prefer not to run each of your# examples within a transaction, remove the following line or assign false# instead of true. config.use_transactional_fixtures=true# You can uncomment this line to turn off ActiveRecord support entirely.# config.use_active_record = false# RSpec Rails can automatically mix in different behaviours to your tests# based on their file location, for example enabling you to call `get` and# `post` in specs under `spec/controllers`.## You can disable this behaviour by removing the line below, and instead# explicitly tag your specs with their type, e.g.:## RSpec.describe UsersController, type: :controller do# # ...# end## The different available types are documented in the features, such as in# https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location!# Filter lines from Rails gems in backtraces. config.filter_rails_from_backtrace!# arbitrary gems may also be filtered via:# config.filter_gems_from_backtrace("gem name")# Devise Test helpers config.includeDevise::Test::ControllerHelpers,type::controller config.includeDevise::Test::IntegrationHelpers,type::requestend
Agora podemos começar a montar os testes das nossas controllers.
Suponha que na sua aplicação o usuário(user) tenha produtos favoritos que ele quer adicionar para poupar tempo. Dessa forma, na controller de favoritos, você pode querer colocar uma função para ver favoritos e outra para criar favoritos:
classApi::V1::FavouriteController<ApplicationController acts_as_token_authentication_handler_for Userdefindex favourites = current_user.favourites render json: favourites,status::okenddefcreate favourite = Favourite.new(favourites_params)if current_user.id=== favourite.user_idthen favourite.save! render json: favourite,status::createdelsif favourite.user_id===nilthen render json: {message:"unprocessable_entity" },status::unprocessable_entityelse render json: { message:"Você não pode criar um favorito para outro usuário" },status::unauthorizedendrescueStandardError=> e render json: {message: e.message},status::unprocessable_entityendend
Como iríamos testar esses métodos se eles requerem o user estar logado? A resposta é simples: precisamos criar o user e usar o token e o email dele nos headers quando chamar o get/post:
describe "/GET #index"dolet(:user) { create(:user) }let(:product) { create(:product,name:'product1') } let(:product2) { create(:product,name:'product2')} before docreate(:favourite,user_id: user.id,product_id: product.id)create(:favourite,user_id: user.id,product_id: product2.id)end context 'logged in as user' before do get '/api/v1/favourites/',headers: {'X-User-Token': user.authentication_token,'X-User-Email': user.email }end it { expect(response).tohave_http_status(:ok) } it 'returns with json'doexpect(response.content_type).toeq('application/json; charset=utf-8')end it 'returns 2 elements'doexpect(JSON.parse(response.body).size).toeq(2)endend
E a mesma coisa faríamos para testar o método post:
...let(:user) { create(:user) }let(:product) { create(:product) }let(:params)do {product_id: product.id,user_id: user.id}endcontext 'logged in as user with valid params'do before do post "/api/v1/favourites/create",params: { favourite: params },headers: {'X-User-Token': user.authentication_token,'X-User-Email': user.email }end it { expect(response).tohave_http_status(:created) } it 'creates the favourite'do new_favourite = Favourite.find_by(user_id: user.id,product_id: product.id)expect(new_favourite).not_to be_nilendend...
E quando o user não está logado, como testamos a falha na autenticação?
context 'not logged in as user'do before do post "/api/v1/favourites/create",params: { favourite: params }end it 'returns a failure response'doexpect(response).to redirect_to authentication_failure_pathendend
Como mostrado, usamos o authentication_failure_path que criamos na configuração do devise. Dessa forma, como no post não foram informados o token e o email do user, a resposta foi redirecionada para o authentication_failure_path e no teste conseguimos testar isso também.