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
I'm trying to write some rspec tests to check API endpoints for an API-only application.
Testing error
Failure/Error: expect( res ).to be_success
expected 200 to respond to `success?`
But if the same call (with full api url) is made from another application it works fine and returns a response.
Example from other application:
res = RestClient.get "site.io/api/v1/projects/1"
p JSON.parse(res)
Blog example I'm trying to follow: (http://matthewlehner.net/rails-api-testing-guidelines/).
# spec/requests/api/v1/messages_spec.rb
describe "Messages API" do
it 'sends a list of messages' do
FactoryGirl.create_list(:message, 10)
get '/api/v1/messages'
json = JSON.parse(response.body)
# test for the 200 status-code
expect(response).to be_success
# check to make sure the right amount of messages are returned
expect(json['messages'].length).to eq(10)
end
end
My Application
/requests/projects_spec.rb
require 'rails_helper'
RSpec.describe Project do
describe "show_project" do
before do
#project1 = create(:project)
end
it "Checks if responds successfully" do
res = get '/api/v1/projects/1'
expect( res ).to be_success
end
end
end
/factories/projects.rb
FactoryGirl.define do
factory :project do
name "Thing"
key "123123"
end
end
routes.rb
namespace :api, :defaults => { :format => 'json'} do
namespace :v1 do
resources :projects, only: [:create, :show]
end
end
end
I don't have much experience with testing, so if anyone can point me in the correct direction I would really really appreciate it.
When using Rspec Request Specs, your call to get '/api/v1/projects/1' doesn't need to captured by your res variable. Spec Request tests automatically set the value of response when get '/api/v1/projects/1' is run. The example you're following is correct, it just looks like your missing some knowledge about how much Rspec is handling for you behind the scenes. This makes your test simpler:
it "Checks if responds successfully" do
get '/api/v1/projects/1'
expect(response).to be_success
end
In Rspec Request tests, response is automatically setup by the call the get without you needing to do anything extra.
I'm working on a test for my Rails 4 app and I'm pretty new to using RSpec. I have a controller named AppsController which has the standard index, new, show, create... methods and they all work the way Rails suggest Etc. "new" creates a new instance of the object and create actually saves it, show, shows it and index shows all of the object. Here are my current tests can anyone see any potential problems or things that i could improve?
FactoryGirl.define do
factory :developer do
email 'example#me.com'
password 'new_york'
password_confirmation 'new_york'
tos '1'
end
factory :app do
name 'New App'
tos '1'
end
factory :invalid_app, parent: :app do
name 'nil'
tos '0'
end
end
require 'spec_helper'
def create_valid!
post :create, app: app_attributes
end
def create_invalid!
post :create, app: app_invalid_attributes
end
def show!
get :show, id: app
end
def update_valid!
put :update, id: app, app: app_attributes
end
def update_invalid!
put :update, id: app, app: app_invalid_attributes
end
def delete!
delete :destroy, id: app
end
def http_success
expect(response).to be_success
end
def expect_template(view)
expect(response).to render_template(view)
end
describe AppsController do
render_views
before(:each) do
#developer = FactoryGirl.create(:developer)
#developer.confirm!
sign_in #developer
end
let(:app) { FactoryGirl.create(:app, developer: #developer) }
let(:app_attributes) { FactoryGirl.attributes_for(:app) }
let(:app_invalid_attributes) { FactoryGirl.attributes_for(:invalid_app) }
describe 'GET #index' do
it 'responds with an HTTP 200 status' do
get :index
http_success
end
it 'renders the :index view' do
get :index
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
app = FactoryGirl.create(:app, :developer => #developer)
get :index
expect(assigns(:app)).to eq([app])
end
end
describe 'POST #create' do
context 'with valid parameters' do
it 'creates a new app' do
expect { create_valid!
}.to change(App, :count).by(1)
end
it 'redirects to the new app keys' do
create_valid!
expect(response).to redirect_to keys_app_path(App.last)
end
end
context 'with invalid parameters' do
it 'does not create the new app' do
expect { create_invalid!
}.to_not change(App, :count)
end
it 'renders the :new view' do
create_invalid!
expect_template(:new)
end
end
end
describe 'GET #show' do
it 'responds with an HTTP 200 status' do
show!
http_success
end
it 'renders the :show view' do
show!
expect_template(:show)
end
it 'populates #app with the requested app' do
show!
expect(assigns(:app)).to eq(app)
end
end
describe 'PUT #update' do
context 'with valid parameters' do
it 'locates the requested app' do
update_valid!
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
update_valid!
expect(app.name).to eq('Updated App')
end
it 'redirects to the updated app' do
update_valid!
expect(response).to redirect_to app
end
end
context 'with invalid parameters' do
it 'locates the requested app' do
update_invalid!
expect(assigns(:app)).to eq(app)
end
it 'does not change app attributes' do
update_invalid!
expect(app.name).to_not eq('Updated App')
end
it 'renders the :edit view' do
update_invalid!
expect_template(:edit)
end
end
end
describe 'DELETE #destroy' do
it 'deletes the app' do
expect { delete!
}.to change(App, :count).by(-1)
end
it 'redirects to apps#index' do
delete!
expect(response).to redirect_to apps_url
end
end
end
count should have been changed by -1, but was changed by 0 - on DELETE #destroy
expecting <"new"> but rendering with <[]> - on POST #create
expected: "Updated App"
got: "New App" - on PUT #update
expecting <"edit"> but rendering with <[]> - on PUT #update
expected: [#<App id: nil, unique_id: "rOIc5p", developer_id: 18, name: "New App">]
got: nil - on GET #index
Small thing - your #http_success method is testing the exact same thing twice.
You could also factor out the references to app by putting a #let statement right after your #before block:
let(:app) { FactoryGirl.create(:app, developer: #developer) }
then in your specs, just
it 'renders the :show view' do
get :show, id: app
expect_template(:show)
end
Edit:
Then the order of operations will be 1) the #developer is created in the #before block, 2) the spec is entered, 3) at the first reference to app in the spec, the #let block will create an instance of an app.
That means you can't factor out the app creation in the #index spec, because in that case the spec will call the action before it creates the app.
A few things I thought of, reading your code:
You don't need to include parens on method calls taking no arguments. Just http_success will work.
You should get try to use the modern RSpec expectation syntax consistently. Instead of assigns(:app).should eq(app), use expect(assigns(:app)).to eq(app). (There's one exception to this, which is expectations on mocks (ie. should_receive(:message)), which will only take on the modern expect-to syntax as of RSpec 3.
For controller specs, I like to create little methods for each action that actually invokes the action. You'll notice you call get :show, id: app several times in the GET #show specs. To DRY up your specs a little more, you could instead write the following method within the describe block:
def show!
get :show, id: app
end
Try to use one Hash syntax consistently. What's more, Rails 4 can't be run with Ruby 1.8, so there's (nearly) no reason to use the hash-rocket Hash syntax.
If I'm getting really, really picky, I usually consider instance variables in a spec to be a smell. In almost all cases, instance variables should be refactored to a memoized let/given blocks.
If I'm getting really, really, really picky, I prefer to think of controller specs such as yours as strictly a unit test of the controller, not an integration test (that's what Capybara is for), and as such you shouldn't be exercising your model layer at all. You should only be testing that your controller is sending the right messages to the model layer. In other words, all the model layer stuff should be stubbed out. For example:
describe 'GET #show' do
let(:app) { stub(:app) }
before do
App.stub(:find).and_return(app)
end
it 'populates #app' do
get :show, id: app
assigns(:app).should eq(app)
end
end
I know this last is a personal preference, not a metaphysical truth or even necessarily a wide-spread standard convention, so you may choose to take it or leave it. I prefer it, because it keeps my specs very speedy, and gives me a very clear heuristic for when my controller actions are doing too much, and I might need to consider refactoring. It could be a good habit to get into.
First, I'm not certain but I suspect your invalid app factory may be wrong. Did you mean
factory :invalid_app, parent: :app do
name nil
tos '0'
end
nil as a ruby NilClass not "nil" as a string?
As for other comments about cleanup and stuff, here are a few of my thoughts.
You can avoid the need for some of your helper methods and duplication by using before blocks for each describe. Taking just your index tests you could have something more like
describe 'GET #index' do
before do
get :index
end
it 'responds with an HTTP 200 status' do
http_success
end
it 'renders the :index view' do
expect_template(:index)
end
it 'populates #apps with the current_developers apps' do
expect(assigns(:app)).to eq([app])
end
end
Notice also that you don't need to recreate app because your let is doing it as necessary.
On the failures, I suspect the delete count change could be failing because inside the expectation, the test framework is creating a new app (from the let) and then deleting it leading to a count change of 0. For that test, you need to make sure you're app is created outside of your expectation. Because you are using let, you could do that like this:
describe 'DELETE #destroy' do
it 'deletes the app' do
# ensure that app is already created
app
expect {
delete!
}.to change(App, :count).by(-1)
end
end
alternatively, change the let to a let! which will force the creation before the specs actually run.
As for other failures, thought #DanielWright suggested the helper methods, I find those complicate the debug. I can't see where you set the app name to "Updated App", for example. Perhaps a clearer test (for that particular one) would not use the helper methods but could be more explicit. Something like
describe 'PUT #update' do
let(:app_attributes) { FactoryGirl.attributes_for(:app, name: 'The New App Name') }
before do
put :update, id: app, app: app_attributes
end
context 'with valid parameters' do
it 'locates the requested app' do
expect(assigns(:app)).to eq(app)
end
it 'changes app attributes' do
# notice the reload which will make sure you refetch this from the db
expect(app.reload.name).to eq('The New App Name')
end
it 'redirects to the updated app' do
expect(response).to redirect_to app
end
end
end
For the other errors, you might want to start debugging your code. Are you certain it should work? Have you looked at output logs? Maybe the tests are doing there job and finding errors in your controller code. Have you done any step-through debugging?
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 am working in Rspec of ROR..
I am trying to test my controllers using RSpec.i am having a Users controller with functions like new , tags, etc..
i created a file under spec/users_controller_spec.rb
and added the test cases as.
require 'spec_helper'
describe UsersController do
integrate_views
it "should use UsersController" do
controller.should be_an_instance_of(UsersController)
end
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
it "should have the title" do
get 'new'
response.should have_tag("title", "First app" )
end
end
end
which gets pass.
But when i add a test case for tags ..
like
describe "GET 'tags'" do
it "should be successful" do
get 'tags'
response.should be_success
end
end
this results in an error as
F...
1)
'UsersController GET 'tags' should be successful' FAILED
expected success? to return true, got false
why it is coming like this ?? i am very new to ROR and cant find the reason of why i am getting this error..
How to make this pass .
Also i tried the Url
http://localhost:3000/users/tags which is running for me .. But on testing using $spec spec/ i am getting the error ..
Your test may be failing for any number of reasons. Does the route require an ID in the parameter hash? Is the controller action redirecting? Is the controller raising an error?
You'll need to look at the controller code /and/or routes.rb to discover the cause of the failure. Take note of before filters in the controller, which may not allow the action to be reachable at all.
You need to add custom routes that are not within the default 7 routes. Assuming you have resources :users within your routes you will need to modify it. I'm also assuming that your tags route is unique to individual users.
resources :users do
member do
# creates /users/:user_id/tags
get :tags
end
end
And in your RSpec test you would call it like
describe '#tags' do
user = create :user
get :tags, user_id: user.id
end
If the route is not to be unique per user the other option is a collection route, something like:
resources :users do
collection do
# creates /users/tags
get :tags
end
end