How to Test Api In Rails Rspec - ruby-on-rails

I'm new to rails testing,i want to test my rails controller api.
I using gems rails-rspec ,capybara and database-cleaner like this in my Gemfile:
group :development, :test do
gem 'rspec-rails', '~> 3.7'
end
group :test do
gem 'database_cleaner'
gem 'capybara'
end
And in my spec folder i create a controller and create a user_controller_spec.rb inside my api folder and want to test create a user with valid parameters and write a code like this:
RSpec.describe "API V1 Users", type: 'request' do
describe "POST /api/v1/users" do
context "with valid parameters" do
let(:valid_params) do
{
fname: "Riya",
lname: "******",
email: "*******.com",
password: "",
contact: "2144333",
country_code:"91",
address: "Sector 63",
city: "Noida",
state: "UP",
country: "India",
zipcode: "23001",
role: "***",
image: ""
}
end
it "creates a new user" do
expect { post "/api/v1/users", params: valid_params}
expect(response).to have_http_status(:ok) # it's use for code 200
end
it "creates a user with the correct attributes" do
post "/api/v1/users", params: valid_params
expect(User.last).to have_attributes valid_params
end
end
context "with invalid parameters" do
# testing for validation failures is just as important!
# ...
end
end
end
But it gives me error :
Failure/Error: expect(User.last).to have_attributes valid_params expected nil to respond to :fname, :lname, :email, :password, :contact

To be sure that your post query create an user you can write it like this:
expect { post "/api/v1/users", params: valid_params}.to change(User, :count).by(1)
It test if a user is added to the database.

Related

Problem with Rspec testing sign_in (Devise) with Request and rswag

I was tempted to use rswag with rspec to document REST API and write test at the same time.
I am trying to fallow tutorials and documentations but I cannot get sign_in endpoint working ( devise - session ).
When I do run rspec than I receive status code error.
require 'swagger_helper'
require 'rails_helper'
require 'shared_context'
describe 'Sonaaar REST API', type: :request do
...
path '/users/sign_in' do
post 'Sign In' do
tags 'Session'
consumes 'application/json'
produces 'application/json'
parameter name: :user, in: :body, schema: {
type: :object,
properties: {
email: { type: :string },
password: { type: :string },
},
required: ['email', 'password']
}
response '201', 'sign in', { 'HTTP_ACCEPT' => "application/json" } do
response '201', 'sign in', { 'HTTP_ACCEPT' => "application/json" } do
#
# let(:user) do
# create(:user, email: 'email#domain.com', password: 'Password1')
# end
#Error
# Failure/Error:
# raise UnexpectedResponse,
# "Expected response code '#{response.code}' to match '#{expected}'\n" \
# "Response body: #{response.body}"
#
# Rswag::Specs::UnexpectedResponse:
# Expected response code '401' to match '201'
# Response body: {"error":"You need to sign in or sign up before continuing."}
let(:user) { { user: { login: 'email#domain.com', password: 'Password1' } } }
# Rswag::Specs::UnexpectedResponse:
# Expected response code '401' to match '201'
# Response body: {"error":"You need to sign in or sign up before continuing."}
# /Users/filip/.rvm/gems/ruby-2.7.1/gems/rswag-specs-2.4.0/lib/rswag/specs/r
run_test!
end
Than i do have RSpec error:
1) Sonaaar REST API /users/sign_in post sign in returns a 201 response
Failure/Error:
raise UnexpectedResponse,
"Expected response code '#{response.code}' to match '#{expected}'\n" \
"Response body: #{response.body}"
Rswag::Specs::UnexpectedResponse:
Expected response code '401' to match '201'
Response body: {"error":"You need to sign in or sign up before continuing."}
Authentication: JWT-token
Content Type: application/json
Stack/Gems:
Ruby on Rails (Rails 6.1.2.1)
devise (4.7.3)
devise-jwt (0.7.0)
rspec-rails (4.0.2)
rswag (2.4.0) - https://github.com/rswag/rswag
Request specs need a little bit more help than just the normal warden helpers that work for feature specs:
RSpec.configure do |config|
config.include Warden::Test::Helpers
end
Add to rails_helper or another file and require in rails_helper:
module DeviseRequestSpecHelpers
include Warden::Test::Helpers
def sign_in(user)
login_as(user, scope: :user)
end
def sign_out
logout(:user)
end
end
Then in rails_helper add
RSpec.configure do |config|
config.include DeviseRequestSpecHelpers, type: :request
end
You should be able to login now in request specs with:
context 'some context' do
scenario 'some scenario' do
login_as(user)
end
end
as #sam said, but actually you can just include the warden helpers
RSpec.configure do |config|
config.include Warden::Test::Helpers
end
And then
let(:admin) { create(:admin) } # for this case it's devise :admins
before do
login_as(admin, scope: :admin)
end
# If you want to logout admin
after do
logout(:admin)
end
it 'should return 200 'do
get 'some_path_that_needs_session'
expect(response).to have_http_status(200)
end
You can refer to [Warden::Test::Helpers] (https://www.rubydoc.info/github/hassox/warden/Warden/Test/Helpers) docs

Factory not defined for RSPEC test Ruby

I 'm trying to write an RSpec test for this service. The service below essentially just makes a call to some endpoint and then create data in the postgres DB.
class SomeService
API_URL = "someEndPoint"
def process
get_reviews
end
def get_reviews
conn = Faraday.new(url: "#{APIURL}/#{ENV["ID"]}/reviews?language=en&stars=5") do |faraday|
faraday.headers["apikey"] = ENV["KEY"]
faraday.adapter Faraday.default_adapter
end
response = conn.get
imported_reviews = []
results = JSON.parse(response.body)
results["reviews"].each do |item|
review = SomeModel.create(
title: item["title"],
body: item["text"],
posted_at: item["createdAt"],
link: "https://somelink.com/#{item["id"]}",
display_name: item["consumer"]["displayName"]
)
imported_reviews << review
end
imported_reviews
end
end
My model is just the boilerplate model shown as:
class SomeModel < ApplicationRecord
end
This is my spec file some_service_spec.rb. I've written plenty of tests in JEST but it's just so foreign to me in Ruby.
# frozen_string_literal: true
require 'rails_helper'
describe TrustpilotService do
describe "#process" do
context 'with valid API_KEY' do
before :all do
WebMock.allow_net_connect!
end
after :all do
WebMock.disable_net_connect!(allow_localhost: true)
end
it "should import reviews to database" do
key = "90e0dbc7160d4024a1f12bc24b3d1def" #fake key#
some_review = create(title: 'Blah', body: 'Blah blah', posted_at: '2019-09-30T20:35:14Z', link: 'https://somelink.com/reviews/asdf123', display_name: 'Jane Doe')
some_review.save!
service = described_class.new
expect{ service.process }.to change{ SomeModel.count }
end
end
end
end
Edit added my factory for reference
FactoryBot.define do
factory :trustpilot_review do
end
end
Gem File
group :test do
gem "capybara", "~> 3.29.0"
gem 'database_cleaner'
gem 'factory_bot_rails'
gem 'rails-controller-testing'
gem 'rspec-rails', '~> 3.8'
gem 'rspec-snapshot', '~> 0.1.2'
gem "shoulda-matchers"
gem "simplecov", require: false
gem 'timecop'
gem "webdrivers"
gem 'webmock'
end
I essentially was just trying to replicate another spec file that was testing a service that does sort of the same thing but I'm getting Factory not registered: as an error. I created a factory but not entirely sure what to even put in it. All of the info on RSPEC out there is kind of outdated. As you can tell, I'm pretty new to Ruby so any help is appreciated.
If this is the way you are calling your factory, then you missed naming the factory:
some_review = create(title: 'Blah', body: 'Blah blah', posted_at: '2019-09-30T20:35:14Z', link: 'https://somelink.com/reviews/asdf123', display_name: 'Jane Doe')
Try
some_review = create(:trustpilot_review, title: 'Blah', body: 'Blah blah', posted_at: '2019-09-30T20:35:14Z', link: 'https://somelink.com/reviews/asdf123', display_name: 'Jane Doe')
Also it seems to me that the title, link, etc, should mostly be in the factory definition which is the point of factories.
If you want to use factory name with different name of model name. then need to add class_name like below:
FactoryBot.define do
factory :trustpilot_review, class: 'SomeModel' do
end
end

Testing Rails API controller POST with RSpec

As the title suggests I'm just trying to test the create action in my API controller with RSpec. The controller looks something like:
module Api
module V1
class BathroomController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:create]`
def create
bathroom = Bathroom.new(bathroom_params)
bathroom.user = current_user
if bathroom.save
render json: { status: 'SUCCESS', message: 'Saved new bathroom', bathrooms: bathroom }, status: :ok
end
end
private
def bathroom_params
params.require(:bathroom).permit(:establishment, :address, :city, :state, :zip, :gender, :key_needed, :toilet_quantity)
end
end
end
end
Right now this is doing exactly what it should which is great. The test however...not so much. Here's what I have for the test portion:
describe "POST #create" do
let!(:bath) {{
establishment: "Fake Place",
address: "123 Main St",
city: "Cityton",
state: "NY",
zip: "11111",
gender: "Unisex",
key_needed: false,
toilet_quantity: 1
}}
let!(:params) { {bathroom: bath} }
it "receives bathroom data and creates a new bathroom" do
post :create, params: params
bathroom = Bathroom.last
expect(bathroom.establishment).to eq "Fake Place"
end
end
I'm sure there's more than one thing wrong here but I'm having trouble finding much information about the right way to go about testing this. Any insight or suggestions would be greatly appreciated.
I would skip controller specs altogether. Rails 5 has pretty much delegated ActionController::TestCase (which RSpec wraps as controller specs) to the junk drawer. Controller tests don't send real http requests and stub out key parts of Rails like the router and middleware. Total depreciation and delegation to a separate gem will happen pretty soon.
Instead you want to use a request spec.
RSpec.describe "API V1 Bathrooms", type: 'request' do
describe "POST /api/v1/bathrooms" do
context "with valid parameters" do
let(:valid_params) do
{
bathroom: {
establishment: "Fake Place",
address: "123 Main St",
city: "Cityton",
state: "NY",
zip: "11111",
gender: "Unisex",
key_needed: false,
toilet_quantity: 1
}
}
end
it "creates a new bathroom" do
expect { post "/api/v1/bathrooms", params: valid_params }.to change(Bathroom, :count).by(+1)
expect(response).to have_http_status :created
expect(response.headers['Location']).to eq api_v1_bathroom_url(Bathroom.last)
end
it "creates a bathroom with the correct attributes" do
post "/api/v1/bathrooms", params: valid_params
expect(Bathroom.last).to have_attributes valid_params[:bathroom]
end
end
context "with invalid parameters" do
# testing for validation failures is just as important!
# ...
end
end
end
Also sending a bunch of junk like render json: { status: 'SUCCESS', message: 'Saved new bathroom', bathrooms: bathroom }, status: :ok is an anti-pattern.
In response you should just send a 201 CREATED response with a location header which contains a url to the newly created resource or a response body that contains the newly created resource.
def create
bathroom = current_user.bathrooms.new(bathroom_params)
if bathroom.save
head :created, location: api_v1_bathroom_url(bathroom)
else
head :unprocessable_entity
end
end
If your client can't tell by looking at the response code if the response is successful or not you're doing it wrong.
You don't really need to test the values from the record saved on the database, you could do something like:
expect(post :create, params: params).to change(Bathroom, :count).by(1)
That's enough to test that the create action creates a record on the desired table.
Then you can add more specs to test that Bathroom.new receives the expected parameters (that way you know that it would have those fields when saved), or stub the bathroom object and it's save method to test the response.
If you want to test that the saved record has the right values, I think that spec belongs to the Bathroom model and not the controller (or better, an integration test).
So I followed the advice of max but made one slight change to get it working. My final code was:
RSpec.describe "API V1 Bathrooms", type: 'request' do
describe "POST /api/v1/bathrooms" do
context "with valid parameters" do
let(:valid_params) do
{
bathroom: {
establishment: "Fake Place",
address: "123 Main St",
city: "Cityton",
state: "NY",
zip: "11111",
gender: "Unisex",
key_needed: false,
toilet_quantity: 1
}
}
end
it "creates a new bathroom" do
user = FactoryGirl.create(:user, email: "email1#website.com")
login_as(user, :scope => :user)
expect { post "/api/v1/bathrooms", params: valid_params }.to change(Bathroom, :count).by(+1)
expect(response).to have_http_status :created
expect(response.headers['Location']).to eq api_v1_bathroom_url(Bathroom.last)
end
it "creates a bathroom with the correct attributes" do
user = FactoryGirl.create(:user, email: "email2#website.com")
login_as(user, :scope => :user)
post "/api/v1/bathrooms", params: valid_params
expect(Bathroom.last).to have_attributes valid_params[:bathroom]
end
end
end
end
The key was to use FactoryGirl to create a new user because the bathroom needs an associated user_id to be valid.

What is the best approach to test JSON API using RSpec request spec

I find it troubling to test JSON API using RSpec request spec (see code below), because I have to create the required parameter every time (valid_attributes and invalid_attributes and it bothers me a LOT) I need to send a new request. It becomes harder to test when I need to send a request with an Authentication token (another requests?).
Is there a better approach to do so?
describe 'POST /users' do
# valid payload
let(:valid_attributes) {
{
data: {
attributes: { email: 'sample#mail.com', password: '1234' },
type: 'user'
}
}
}
# invalid payload
let(:invalid_attributes) {
{
data: {
attributes: { email: '', password: '' },
type: 'user'
}
}
}
context 'when the request is valid' do
before { post '/users', params: valid_attributes }
it 'creates a user' do
expect(response).to have_http_status(201)
end
end
context 'when the request is invalid' do
before { post '/users', params: invalid_attributes }
it 'create a user' do
expect(response).to have_http_status(422)
end
end
end
The gems I used for testing,
group :test do
gem 'rspec-rails', '~> 3.5'
# Use Factory Girl for generating random test data
gem 'factory_girl_rails', '~> 4.0'
gem 'shoulda-matchers', '~> 3.1'
gem 'faker'
gem 'database_cleaner'
end
Not sure what you had in mind, but I'd remove duplication like this:
def user_with(email: '', password: '')
{
data: {
attributes: { email: email, password: password },
type: 'user'
}
}
end
# valid payload
let(:valid_attributes) { user_with(email: 'sample#mail.com', password: '1234') }
# invalid payload
let(:invalid_attributes) { user_with() }
Obviously move that in a module or shared_context in some file in support dir if you need to reuse it accross several spec files.

possible to access session variables in Rspec with Capybara

Is it possible to access session variables in Rspec with Capybara and Selenium drive?
How would I do something like this (I am using omniauth-facebook)?
require 'spec_helper'
describe 'Api' do
def login_with_oauth
visit "/auth/facebook"
end
it 'get location count for specific user' do
login_with_oauth
#user=User.find(session[:user_id])
#puts "here is response body: #{response.body}"
I see
How to inspect the session hash with Capybara and RSpec? but doesn't get me what I really want.
thx
I suppose to think what you actually don't need the session. I'm sorry if I'm wrong.
That's how you can test omniauth.
# spec/spec_helper.rb
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new
OmniAuth.config.add_mock(:facebook, {
provider: "facebook",
uid: "123456",
info: {
email: "bruce.wayne#batman.com",
first_name: "Bruce",
last_name: "Wayne"
}
})
# spec/request/api_spec.rb
let!(:user) { create :user, email: "bruce.wayne#batman.com" }
before do
visit "/auth/facebook"
end
it 'get location count for specific user' do
user.id # contains your user id
end

Resources