So I am having some trouble getting some RSpec tests to FAIL. No matter what I try they are always passing, well, one of the tests works correctly and I verified it can fail if I modify the code it is testing.
I am trying to test the content of JSON responses made to an external API to make sure my controller is sorting these return JSON objects correctly.
Am I missing something here? Why cant I get these to fail?
RSpec.describe 'Posts', type: :request do
describe 'ping' do
it 'returns status 200' do # This text block works correctly.
get "/api/ping"
expect(response.content_type).to eq("application/json; charset=utf-8")
expect(response).to have_http_status(200)
end
end
describe 'get /api/posts' do # all tests below always pass, no matter what.
it 'should return an error json if no tag is given' do
get "/api/posts"
expect(response.content_type).to eq("application/json; charset=utf-8")
expect(response.body).to eq("{\"error\":\"The tag parameter is required\"}")
end
it 'should return a json of posts' do
get "/api/posts?tags=tech" do
expect(body_as_json.keys).to match_array(["id", "authorid", "likes", "popularity", "reads", "tags"])
end
end
it 'should sort the posts by id, in ascending order when no sort order is specified' do
get "/api/posts?tags=tech" do
expect(JSON.parse(response.body['posts'][0]['id'].value)).to_be(1)
expect(JSON.parse(response.body['posts'][-1]['id'].value)).to_be(99)
end
end
it 'should sort the posts by id, in descending order when a descending order is specified' do
get "/api/posts?tags=tech&direction=desc" do
expect(JSON.parse(response.body['posts'][0]['id'].value)).to_be(99)
expect(JSON.parse(response.body['posts'][-1]['id'].value)).to_be(1)
end
end
end
Within the get block of the 'should return an error json if no tag is given' do block I even tried expect(4).to eq(5) and even THIS passed!
Help much appreciated here!
The 'get' should not have a do block. This will cause the test to always pass.
so this:
it 'should sort the posts by id, in descending order when a descending order is specified' do
get "/api/posts?tags=tech&direction=desc" do. # <<< remove this
expect(JSON.parse(response.body['posts'][0]['id'].value)).to_be(99)
expect(JSON.parse(response.body['posts'][-1]['id'].value)).to_be(1)
end # <<< and this from all tests.
end
should look like this:
it 'should sort the posts by id, in descending order when a descending order is specified' do
get "/api/posts?tags=tech&direction=desc"
expect(JSON.parse(response.body['posts'][0]['id'].value)).to_be(99)
expect(JSON.parse(response.body['posts'][-1]['id'].value)).to_be(1)
end
Related
I am new to RSpec but here I am trying to create tests based on this code and I am keep on getting this error. Any suggestions?
CODE:
serialization_scope nil
before_action :set_list, only: [:show, :destroy, :update]
before_action :verify_user, only: :show
def create
#list = current_user.lists.build(list_params)
if #list.save
render json: {message: ['Success']}, status: 200
else
render json: {errors:[#list.errors.full_messages]}, status: 400
end
end
Here is the RSpec file that I started :
require "rails_helper"
RSpec.describe V1::ListsController, :type => :controller do
describe "POST create" do
it "returns HTTP status" do
expect(post :create).to change(#list, :count).by(+1)
expect(response).to have_http_status :success #200
end
end
describe 'GET status if its not created' do
it "return HTTP status - reports BAD REQUEST (HTTP status 400)" do
expect(response.status).to eq 400
end
end
end
And the error that I got is :
Failures:
1) V1::ListsController GET status if its created returns HTTP status
Failure/Error: expect(post :create).to change(#list, :count).by(+1)
expected #count to have changed by 1, but was not given a block
# ./spec/controllers/lists_controller_spec.rb:8:in `block (3 levels) in <top (required)>'
2) GET status if its not created return HTTP status - reports BAD REQUEST (HTTP status 400)
Failure/Error: expect(response.status).to eq 400
expected: 400
got: 200
(compared using ==)
Try this code.
require 'rails_helper'
RSpec.describe V1::ListsController, type: :request do
describe 'valid request' do
it 'returns HTTP status' do
post '/list', params: { list: { list_name: 'xyz' } }
expect(response.status).to eq 201
end
end
describe 'invalid request' do
it "should return unauthorized" do
post '/list'
assert_response :unauthorized
end
end
end
In params you need to pass your list_params.
Spec would look like:
describe "POST create" do
context 'valid request' do
it 'should increase #list item' do
expect { post :create }.to change(List, :count).by(1)
end
it "returns HTTP status" do
post :create
expect(response).to have_http_status :success #200
end
end
context 'invalid request' do
it "return HTTP status - reports BAD REQUEST (HTTP status 400)" do
get :create
expect(response.status).to eq 400
end
end
end
Cheers!
You can test an object not being created by intentionally causing some of its validations to fail e.g. you can pass a mandatory attribute as nil from the RSpec.
Sample request: post :create, { title: nil }.
But as per your RSpec code, it seems there are no validations on List model. So, lets try to stub save and return false for this particular test.
describe 'GET status if its not created' do
# Assuming your model name is `List`
before { allow_any_instance_of(List).to receive(:save) { false } }
it "return HTTP status - reports BAD REQUEST (HTTP status 400)" do
post :create
expect(response.status).to eq 400
end
end
Please post your model for list and i can update the answer with more appropriate test.
Ishika, let me see if I can help you :)
RSpec official documentation recommends you to use request specs instead of controller specs. That is recommended because Rails 5 deprecated some methods used on controller testings. You can read more about this here at RSpec blog
ps.: You can use controller tests so far, but it can be deprecated in a future major version of RSpec.
There are some notes I left after the code, please read them also.
I would write a request spec like this:
# spec/requests/v1/lists_controller_create_spec.rb
require "rails_helper"
RSpec.describe V1::ListsController do
describe 'success' do
it 'returns ok and creates a list', :aggregate_failures do # :aggregate_failures is available only for RSpec 3.3+
expect do
post '/list', title: 'foo' # This will also test your route, avoiding routing specs to be necessary
end.to change { List.count }.from(0).to(1)
expect(response).to have_http_status(:ok)
end
end
describe 'bad request' do
before do
# This is needed because your controller is not validating the object, but look at my
# comment below (out of the code), to think about this behavior, please.
allow_any_instance_of(List).to receive(:save).and_return(false)
end
it 'returns a bad request and does not create a list' do
expect do
post '/list', title: 'foo' # This will also test your route, avoiding routing specs to be necessary
end.not_to change { List.count }
expect(response).to have_http_status(:bad_request)
end
end
end
Notes:
I suggested using more than 1 expectation by example, that is ok in this spec because they are simple and because I'm using :aggregate_failures option. With this option, if the first expectation fails, the next expectations will also be executed, considering that in this case, the following expectations does not depend on the first one, it is ok to use more than 1 expectation for the example.Reference
You are returning a bad request if the object is not saved, but you are not validating it. If your model has validations that will validate the object there, please adjust the specs to fail the save (instead of using the mock I used) and consider rendering an error message in the response
If you think that making the post inside a expect block, you can do different: Store the count of Lists in a variable before making the post and after the post you test if the variable has changed or not, maybe you think it will be more clear and it will do exactly the same thing in the background.
I have a rails controller that returns only json
def index
if params[:filtered] = 'someValue'
#json = Model.where(some_conditions).to_json
else
#json = Model.where(some_other_conditions).to_json
end
render json: #json
end
What is the correct way to test that the action is returning the expected #json objects?
I've tried the following
describe "GET #index" do
before :each do
get :index, filtered: 'someValue'
end
it { expect( response.body ).to eq 'my expected response' }
end
But I'm getting
Failure/Error: it { expect( response.body ).to eq 'my expected response' }
expected: 'my expected response'
got: "[]"
I'm having trouble determining whether there is a problem in the underlying controller, or whether I've simply written a bad test.
Is response.body the correct way to get the json payload?
Help appreciated!
Both your controller and spec are somewhat off.
You don't need to call to_json on the object that you want to render.
If you use the :json option, render will automatically call to_json
for you.
http://guides.rubyonrails.org/layouts_and_rendering.html
The reason your spec is giving you "[]" is that Model.where(some_conditions) is returning an empty collection. An empty collection renders as an empty array in JSON.
Either the scope does not work as intended or your test setup is flawed. Remember that let variables are lazy loading and you either need to use let! or reference the variable for records to be inserted into the test db.
# polyfill for Rails 4. Remove if you are using Rails 5.
let(:parsed_response) { response.body.to_json }
describe "GET #index" do
# or use fixtures / factories
let!(:model) { Model.create!(foo: 'bar') }
before :each do
get :index, filtered: 'someValue'
end
expect(parsed_response.first["id"].to_i).to eq model.id
end
When I run the following test
RSpec.describe LessonsController, type: :controller do
describe 'GET / index' do
let(:lesson1) {FactoryGirl.create(:lesson)}
let(:lesson2) {FactoryGirl.create(:lesson)}
it 'returns an http success' do
get :index
expect(response).to be_success
end
it 'returns all the lessons' do
get :index
expect(assigns[:lessons]).to eq([])
expect(assigns[:lessons]).to eq([lesson1, lesson2])
end
end
end
The second expect, expect(assigns[:lessons]).to eq([lesson1, lesson2]), fails with expected: [#<Lesson id:...>, #<Lesson id:...>] got: #<ActiveRecord::Relation []>.
But then, when I run the following test and it all passes
RSpec.describe LessonsController, type: :controller do
describe 'GET / index' do
let(:lesson1) {FactoryGirl.create(:lesson)}
let(:lesson2) {FactoryGirl.create(:lesson)}
it 'returns an http success' do
get :index
expect(response).to be_success
end
it 'returns all the lessons' do
get :index
expect(assigns[:lessons]).to eq([lesson1, lesson2])
end
end
end
I am wondering why is it that the second test does not fail? I was expecting the second spec to also fail with the same reason as the first one.
I believe it might be due to the let statement.
With that said, I am running rspec-rails, factory_girl_rails and Rails 4. I don't believe it is due to contamination because this effect still occurs even when I run the test in isolation (focus).
First, I'm guessing your controller has some code like this:
#lessons = Lesson.all
Remember, that returns an ActiveRecord::Relation which may not actually hit the database until the last moment it needs to. Also, once an ActiveRecord::Relation fetches its results, it will not re-fetch them unless you call .reload.
Secondly, remember how let works. Code for a let isn't evaluated until you try to access that a variable. So, you get a situation like this:
describe "Something" do
let(:lesson) { Lesson.create! }
it "makes a lesson" do
# right now there are 0 lessons
lesson
# calling `lesson` caused the lesson to be created,
# now there is 1 lesson
end
end
Third, when you turn an ActiveRecord::Relation into an Array, it executes the real database query (in this case, select * from lessons).
With those things in mind, we can contrast the two test cases.
In the first case, lessons are fetched from the database before the lessons are actually created:
it 'returns all the lessons' do
get :index
# No lessons have been created yet
# `select * from lessons` returns no results
expect(assigns[:lessons]).to eq([])
# `lessons` is cached. It won't query the database again
# calling `lesson1` and `lesson2` creates two lessons, but it's too late
# the result has already been cached as []
expect(assigns[:lessons]).to eq([lesson1, lesson2])
end
In the second case, the lessons are created first, then the database query is executed:
get :index
# calling `lesson1` and `lesson2` creates two lessons
# then the AR::Relation runs a query and finds the two lessons
expect(assigns[:lessons]).to eq([lesson1, lesson2])
To demonstrate this, here is an example that should pass:
get :index
expect(assigns[:lessons]).to eq([])
# this causes the lessons to be created
lessons = [lesson1, lesson2]
# use `.reload` to force a new query:
expect(assigns[:lessons].reload).to eq(lessons)
Also, you could use RSpec's let! to create the lessons before running the example.
UPDATED: I realise now that I've been misreading the diff, and I have a string or symbol on one side of the comparison. Still unsure how I should be putting the expectation in this test however..
I'm new to Rspec and TDD in general, and I've run into this problem. I have a controller that does this:
def index
#users = User.page(params[:page])
end
(I'm using Kaminara to paginate)
And a spec:
describe "when the user DOES have admin status" do
login_admin_user
it "should allow the user access to the complete user list page" do
get :index
response.response_code.should == 200
end
describe "and views the /users page" do
before(:each) do
User.stub(:page) {[ mock_model(User), mock_model(User), mock_model(User) ]}
end
it "should show all users" do
get :index
assigns (:users).should =~ User.page
end
end
end
The spec fails with the following:
Failure/Error: assigns (:users).should =~ User.page
expected: [#<User:0x5da86a8 #name="User_1004">, #<User:0x5d9c90c #name="User_1005">, #<User:0x5d93ef6 #name="User_1006">]
got: :users (using =~)
Diff:
## -1,4 +1,2 ##
-[#<User:0x5da86a8 #name="User_1004">,
- #<User:0x5d9c90c #name="User_1005">,
- #<User:0x5d93ef6 #name="User_1006">]
+:users
Those result sets look identical. Why does this spec fail? Thanks in advance!
I think the problem is the space after assigns. It's comparing the symbol :users to your list. Change it to:
assigns(:users).should =~ User.page
And just a note on how to read Rspec failures. The part after expected, is what you gave to should, whereas the part after got is the value your code actually produced. So it's clear from the report that the result sets were not identical.
I'm facing some problems when trying to assert that a method in a Mongoid::Document class is invoked by my controller code:
require 'spec_helper'
describe AController do
describe 'GET index' do
it 'returns the full list' do
get :index
Model.should_receive(:find).with(:all)
response.code.should eq ("200")
end
end
end
Looking at test.log i can see the the query being executed against the database. BUT, the test fails with rspec complaining that Model.find(:all) was expected once, but received 0 times. Anyone got an idea of what is happening here? It seems to me that Rspec is not being able to stub classes that include Mongoid::Document.
Thanks!
Sorry, i screwed up, the expectation was supposed to be set before the get
Correct way:
Model.should_receive(:find).with(:all)
get :index
response.code.should eq ("200")