I currently have the following situation:
ChallengeRequestsController #new - creates a ChallengeRequest along with another model needing a recpient_id.
def create
#challenge_request = ChallengeRequest.new(challenge_params)
recipient = User.find(params.require(:recipient_id))
# Rest of method redacted.
end
def challenge_params
params.require(:challenge_request).permit(:action)
end
With this controller, I have an RSpec test as follows:
RSpec.describe ChallengeRequestsController, type: :controller do
describe "POST #create" do
context "with valid challenge request" do
let(:valid_challenge_request) { build(:challenge_request) }
context "with non-exisistent recpient id" do
it "throws an error" do
expect{
post :create, :params => {
:challenge_request => {
:challenge_request => valid_challenge_request
},
:recipient_id => 10000
}
}.to raise_error ActiveRecord::RecordNotFound
end
end
end
end
end
Just for reference, here's the Factory:
FactoryGirl.define do
factory :challenge_request do
action { Faker::LeagueOfLegends.champion }
end
end
All of this code works and the test passes, my question is how can I refactor this in a way that I don't need to use the ugly nesting in the request?
:challenge_request => {
:challenge_request => valid_challenge_request
}
When posting the request without this nesting:
post :create, :params => {
:challenge_request => valid_challenge_request,
:recipient_id => 10000
}
The challenge_request is empty when the ChallengeRequestController receives the request.
It would probably work if you change your valid_challenge_request to be a hash of attributes instead of the model itself.
let(:valid_challenge_request) { FactoryBot.attributes_for(:challenge_request) }
You're trying to send along an instance of a model as a parameter to a controller, and something is doing something (I can't find what's doing what to the model) to try and coerce it into something that a browser might send the controller. That conversion is turning the model into an empty string, which is not present or the value false and thus causes the require to throw the ParameterMissing
Related
I have created a Chatroom Model that first validates for the presence of some fields so in the controller I create the chatroom then check if it is valid by using the .valid? method to determine the response. Now when I created a test model using FactoryBot the test doesn't go past the if statement and it returns a response as if the test has finished.
Code for my action
def create
new_chatroom = Chatroom.create(chatroom_params)
if new_chatroom.valid?
new_chatroom.members.create({ user_id: #current_user[:username] })
render_response(new_chatroom, :created)
else
render_error_response(new_chatroom.errors, :bad_request)
end
end
Code for the factory
FactoryBot.define do
factory :chatroom do
topic { Faker::Lorem.unique.question }
slug { Faker::IndustrySegments.unique.sub_sector }
description { Faker::Lorem.paragraph }
owner { Faker::Name.first_name }
public { true }
end
end
Here is my test
it "creates a new chatroom" do
post :create, params: {
:topic => "test chatroom",
:slug => "code-testing",
:description => "Testing with Rspec",
}
expect(response).to have_http_status(:created)
end
Here is the render_response method:
def render_response(resource, status)
if block_given?
yield(resource, status)
else
render json: resource, :status => status
end
end
Test failure:
Failure/Error: expect(response).to have_http_status(:created)
expected the response to have status code :created (201) but it was :ok (200)
I get this failure and when I try to make it pass(false positive), the coverage shows the response I'm testing against is not what's actually in my action because the rest of the lines starting from the if statement are not covered.
but I thought FactoryBot takes over the whole model creation in the
tests.
No - FactoryBot just provides factories that create model instances. This is widely used as a replacement for fixtures to populate the database before tests. Unlike with fixtures this is not automatic.
Just adding FactoryBot changes absolutely nothing in your application besides the fact that the generators will create the factory file. It does not effect the behaviour of your models in any way.
When testing the creation of resources you need to test that:
Given valid params, then a model should be persisted to the database
Given valid params, then the response should be successful and point to the newly created resource.
Given invalid params, then a model should not be persisted to the database
Given invalid params, then the response should be 422 and an error page should be rendered.
You want to test this with a request spec and not a controller spec.
Request specs provide a high-level alternative to controller specs. In
fact, as of RSpec 3.5, both the Rails and RSpec teams discourage
directly testing controllers in favor of functional tests like request
specs.
require "rails_helper"
RSpec.describe "Chatroom creation", type: :request do
let(:valid_params) do
{
chatroom: {
topic: "test chatroom",
slug: "code-testing",
description: "Testing with Rspec"
}
}
end
let(:invalid_params) do
{
chatroom: {
topic: ''
}
}
end
context "when the parameters are valid" do
it "creates a new chatroom" do
expect do
post '/chatrooms', params: valid_params
end.to change(Chatroom, :count).by(1)
end
it "returns success" do
post '/chatrooms', params: valid_params
expect(response).to have_http_status(:created)
end
end
context "when the parameters are invalid" do
it "does not create a new chatroom" do
expect do
post '/chatrooms', params: invalid_params
end.to_not change(Chatroom, :count)
end
it "returns bad entity" do
post '/chatrooms', params: invalid_params
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
Then we can address the problem with your controller which should read:
class ChatroomsController < ApplicationController
# ...
def create
new_chatroom = Chatroom.new(chatroom_params)
if new_chatroom.save
new_chatroom.members.create(user_id: #current_user[:username])
render_response(new_chatroom, :created)
else
render_error_response(new_chatroom.errors, :bad_request)
end
end
end
You should never use .valid? to check if a record was saved to the database. It only ensures that the model level validations have passed. Not that the INSERT statement from .create actually created a row in the database. See The Perils of Uniqueness Validations for an example of what can happen.
While you can use new_chatroom.persisted? this is the common rails idiom since it gives you a variable that you can manipulate before the record is persisted.
class ChatroomsController < ApplicationController
# ...
def create
new_chatroom = Chatroom.new(chatroom_params)
new_chatroom.members.new(user: current_user)
if new_chatroom.save
render_response(new_chatroom, :created)
else
render_error_response(new_chatroom.errors, :bad_request)
end
end
end
I would not try to test model validity in a controller spec, but rather write request specs (as suggested by the other respondent). You can test the validity of an object on the model itself like this:
context 'valid' do
let(:chatroom) { create :chatroom }
it { expect(chatroom).to be_valid }
end
context 'fails validation' do
let(:chatroom) { create :chatroom, topic: nil }
it { expect(chatroom).not_to be_valid }
end
Resource: https://www.rubydoc.info/gems/rspec-rails/RSpec%2FRails%2FMatchers:be_valid
But if you want to check that actual fields are validated, I recommend using shoulda matchers on the model like this:
it { should validate_presence_of :topic }
it { should validate_presence_of :slug }
Resource: https://github.com/thoughtbot/shoulda-matchers
I have a small Rails application with a database that holds different Languages. Each of these Languages is subject to change, and i want to keep track of their changes.
I do this by creating an Audit record when the object is changed. This Audit has a has_many :languages, through: :audit_language_couplings field which validates the relationship.
class Audit < ApplicationRecord
belongs_to :audit_type
has_many :audit_language_couplings
has_many :languages, through: :audit_language_couplings
validates_presence_of :audit_type, :admin_id, :date, :before
end
class Language < ApplicationRecord
has_many :audit_language_couplings
has_many :audits, through: :audit_language_couplings
validates_presence_of :iso_code, :display_name, :keyboard_layout, :flag_url, :luis_app_identification, :luis_authorized_key, :luis_location
end
The audits are created by calling the create_audit() method in the LanguagesController when the PUT, DELETE or POST method is called. I also have a /languages/:id/audits endpoints that returns all the audits for the given language in JSON.
The create_token() method:
def create_audit(type, admin_id)
#language.audits.create(
audit_type_id: type,
admin_id: admin_id,
date: Time.now.to_date,
before: #language.to_s # TODO: Use the to-be-created to_json() or to_s() method instead.
)
end
This is also the nature of my problem (i think).
I am currently testing my API with RSpec and Factory bot requests. When i create or update a Language in the tests, no Audits are being created for some reason. But i know the code works, because it works when i do it manually with postman in my dev environment.
FactoryBot.define do
factory :language do
iso_code { Faker::Address.country_code }
display_name { Faker::Address.country }
keyboard_layout { Faker::Internet.url }
flag_url { Faker::Internet.url }
luis_app_identification { Faker::Lorem.characters(5) }
luis_authorized_key { Faker::Lorem.characters(5) }
luis_location { Faker::Lorem.characters(5) }
end
end
I have currently structured my tests like this:
describe 'POST /admin/language' do
let(:valid_attributes) do
{
payload: {
iso_code: 'en-US',
display_name: 'English (US)',
keyboard_layout: 'QWERTY',
flag_url: '/public/images/en-US.png',
luis_app_identification: 'test',
luis_authorized_key: 'test',
luis_location: 'test'
}
}
end
context 'when the request is valid' do
before { post '/admin/languages', params: valid_attributes, headers: token_header }
it 'creates a language' do
expect(json['iso_code']).to eq('en-US')
end
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
context 'an audit should be made for the change' do
before { get "/admin/languages/#{language_id}/audits", headers: token_header }
it 'creates 1 audit' do
expect(json.size).to eq 1
end
it 'is an audit of type 1 [ADD]' do
expect(json[0]['audit_type_id']).to eq 1
end
end
end
context 'when the request is invalid' do
before do
post '/admin/languages', headers: token_header, params:
{
payload: {
display_name: 'English (US)',
keyboard_layout: 'QWERTY',
flag_url: '/public/images/en-US.png',
luis_app_identification: 'test',
luis_authorized_key: 'test',
luis_location: 'test'
}
}
end
it 'returns status code 422' do
expect(response).to have_http_status(422)
end
it 'returns a validation failure message' do
expect(response.body).to match(/Validation failed: Iso code can't be blank/)
end
end
end
The test where i check the audits fails, because 0 audits are returned when running the code with RSpec.
I think i am doing something wrong with the factories, but i am not sure, please let me know!
Cheers
Hard to say exactly what's going on based on the code provided, but one thing you can try is changing your create_audit method to:
def create_audit(type, admin_id)
#language.audits.create!(
Adding the ! (bang) to the create method will raise an exception if for some reason the create fails, which should show up in your RSpec logs. This should at least help you get to the source of the problem.
What you need to check is that when the post happens, that the number of Audit instances increases by one. I'd do this like this:
subject { post '/admin/languages', params: valid_attributes, headers: token_header }
it 'creates an audit'
expect { subject }.to change { Audit.count }.by(1)
end
At the moment you test that there is one audit (without checking how many there were before the spec) and via an indirect method (calling another API endpoint after the first).
Both Pennycracker and ReggieB's answers were correct, or actually part of the bigger picture.
The part about the create!() shebang lead me to the actual issue. The factories did not create an AuditType on which the Audit was depending.
ReggieB suggested that my test setup was flawed, because i was testing a nested request.
I have opted to use an altered version of his suggestion, one which better fits my current setup:
context 'when the request is valid' do
before { post '/admin/languages', params: valid_attributes, headers: token_header(admin_id: admin.id) }
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
context 'the audit system has to be updated' do
it 'creates 1 audit' do
expect(Audit.all.size).to eq(1)
end
it 'should have type 1 [ADD]' do
expect(Audit.first.audit_type.id).to eq(1)
end
end
end
The database cleaner gem cleans the database after each example, so checking for the first Audit works the same as expecting a change.
Thanks everyone for the help.
describe '#messages' do
subject do
FactoryGirl.create :foo,
:type => 'test',
:country => 'US'
end
context 'when is not U.S.' do
before{ allow(subject).to receive(:country).and_return('MX') }
describe '#messages' do
subject { super().messages }
it { is_expected.to include 'This foo was not issued in the United States of America.' }
end
end
end
I'm trying to assign an attribute on the subject... I can't seem to get the incantation correct. Do I need a Double here? I'm not sure how that even works, and I apparently can't decipher the docs. Any help is appreciated.
I think you should define the subject variable as a helper method using let. With this, you are defining a helper method that you can use everywhere in the file.
So, I'll modify your code as follows:
describe '#messages' do
let(:subject) do
FactoryGirl.create :foo,
:type => 'test',
:country => 'US'
end
context 'when is not U.S.' do
before{ allow(subject).to receive(:country).and_return('MX') }
describe '#messages' do
subject { super().messages }
it { is_expected.to include 'This foo was not issued in the United States of America.' }
end
end
end
I think the problem here is one of scope. you are calling the code in the before block before you've set up a subject
you'll need to either move the subject out into the outer context, move the before into the inner describe, or set up some alternative way of calling it so that subject is set up before running the before
I am trying to resolve an issue with my rspec test to create an object but the count doesn't seem to change whatever i try. I am sure i am missing something very basic here.
Here is my rspec:
before do
login_account_admin(user)
#group = Factory(:group, :code => "GR_111", :description => "description for GR_111")
Group.stub!(:find).and_return(#group)
end
describe "#create" do
it "should create a new group object" do
group_params = {:code => "NEW_GROUP", :description => "description for NEW_GROUP"}
expect {
post :create, :service_id => service, :cdb_group => group_params, :button => "save", :format => "js"
}.to change(Group, :count).by(1)
end
it "should not create a new group object with invalid code format" do
group_params = {:code => "invalid", :description => "description for invalid code name group"}
expect {
post :create, :service_id => service, :cdb_group => group_params, :button => "save", :format => "js"
}.to_not change(Group, :count)
end
end
"code" parameter can only contain uppercase letters A to Z, 0-9 and _
Here is the controller method definition for #create
def create
#group = Group.new(params[:cdb_group])
respond_to do |format|
if params[:button] == "cancel"
format.js { render "hide_new"}
elsif #group.save
format.js {
render 'show_new_group'
}
format.html { redirect_to(some_path(#service), :notice => 'Group was successfully created.') }
format.xml { head :ok }
end
end
end
Here is the Group model:
class Group < ActiveRecord::Base
validates_uniqueness_of :code
validates_presence_of :code, :description
validates_format_of :code, :without => /[^A-Z0-9_]/ , :message => 'can only contain uppercase letters A to Z, 0-9 and _'
end
Whenever i try to run the rspec test I get the following errors:-
1) GroupsController User As Account Admin goes to #create should create a new group object
Failure/Error: expect {
count should have been changed by 1, but was changed by 0
# ./spec/controllers/groups_controller_spec.rb:51
2) GroupsController User As Account Admin goes to #create should not create a new group object with invalid code format
Failure/Error: expect {
count should not have changed, but did change from 2 to 1
# ./spec/controllers/groups_controller_spec.rb:58
Any help in this regard would be highly appreciated?
Whenever our tests give us unexpected trouble, it's important to take a step back and re-evaluate our approach. Usually, this is an indication of some design problem, either with the code we're testing or with tests themselves.
While it sounds like using a truncation strategy has fixed this particular problem (see more on that below), i would suggest that there is more to learn from the situation.
Consider the two examples from your spec above. The only difference between them comes down to whether the code parameter is valid or not. I would argue that these examples are really testing the Group model, not the controller.
Now, if we're confident in our model test coverage, then we can take a different approach to the controller spec. From the controller's perspective, the model is a collaborator and in general, we always want to avoid indirectly testing collaborators. In this case, we can use a mock to simulate the behavior of the Group model and only test the controller behavior in isolation.
Something like this (please note the code below is incomplete and untested):
# spec/controllers/groups_controller_spec.rb
describe "#create" do
before do
# use a Test Double instead of a real model
#new_group = double(Group)
#params = { :cdb_group => 'stub_cdb_group_param', :service_id => service }
# using should_receive ensures the controller calls new correctly
Group.should_receive(:new).with(#params[:cdb_group]).and_return(#new_group)
end
context "when cancelled responding to js" do
it "renders hide_new" do
post :create, #params.merge({:button => "cancel", :format => "js"})
expect(response).to render_template('hide_new')
end
end
context "with valid params" do
before do
#new_group.should_receive(:save).and_return(true)
end
context "responding to json" # ...
context "responding to html" # ...
context "responding to xml" #...
end
context "with invalid params" do
before do
#new_group.should_receive(:save).and_return(false)
end
# ...
end
end
While the above doesn't specifically address the problem with record counts you were having, i suspect the problem may go away once you isolate your test targets correctly.
If you choose to stick with database truncation, consider using it selectively as described here.
I hope at least some of that helps :).
After fiddling with my spec_helper.rb file. It turns out that i have to change my database cleaning strategy to truncation. Here is my spec_helper file, for reference (https://gist.github.com/aliibrahim/7152042)
I changed this line in my code and disable use of transactional_fixtures
config.use_transactional_fixtures = false
and my database cleaning strategy is now:
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
This gives a clear database before the start/end of every scenario. Hope this helps anyone!
You should test...
1) Group.create(group_params).should be_true after group_params = ...
If this fails, the problem probably related to model or test environment.
2) response.status.should == 302 after post ...
If this fails, the problem probably related to session (authentication / authorization).
3) assigns(:group).should be_valid after post ...
If this fails, the problem probably related to controller.
I'm sorry, but this is beginning to feel like kicking myself in the head. I'm completely baffled by RSpec. Have watched video after video, read tutorial after tutorial, and still I'm just stuck on square one.
=== here is what I'm working with
http://github.com/fudgestudios/bort/tree/master
=== Errors
F
1)
NoMethodError in 'bidding on an item should work'
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
spec/controllers/auction_controller_spec.rb:16:
spec/controllers/auction_controller_spec.rb:6:
Finished in 0.067139 seconds
1 example, 1 failure
=== here is my controller action
def bid
#bid = Bid.new(params[:bid])
#bid.save
end
=== here is my test
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
before(:each) do
#user = mock_user
stub!(:current_user).and_return(#user)
end
it "should work" do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
assigns[:bid].should be_new_record
end
end
=== spec_helper
http://github.com/fudgestudios/bort/tree/master/spec/spec_helper.rb
It's very disheartening to wake for work at 3 a.m. and accomplish nothing for the day. Please understand.
You've got a couple of things backwards in before(:each). Seeing as the example is specifying that the post should increase the count by 1, you're dealing with real records and there is no reason for stubbing anything at all. Also, at this point, since there is only one example, there is no reason to have a before block. I'd do it this way:
describe ItemsController, "bidding on an item" do
fixtures :users
it "should create a new Bid" do
login_as :quentin
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end
One thing I'd recommend is creating these things VERY granularly for now until you understand them better. Start with the expectation (post should change bid count), run the spec and let the failure message guide you to add whatever else you need in the spec or in the code.
Jesse,
It'll still pass if you comment out the 2nd two lines of before(:each), which are having no impact on the "should create a new Bid" example.
The lambda keyword creates an arbitrary block of code that is not executed when you define it, but is actually an object you can assign to a variable and execute later:
the_post = lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
At this point that code is not executed, but we can refer to it with the 'the_post' variable. Now we can send it 'should', followed by 'change ...', like this:
the_post.should change(Bid, :count).by(1)
When this line is executed, a few things happen. The material to the right of 'should' is evaluated first, initializing an rspec matcher object with some instructions. That matcher is the argument to 'should' - the equivalent of this:
matcher = change(Bid, :count).by(1)
the_post.should(matcher)
The 'should' method is called on the_post, which is the code block (that still hasn't been executed). Under the hood, the 'should' method passes self (the_post) to the matcher, so the matcher now has everything it needs to evaluate the example.
The matcher calls Bid.count and records the value. Then it executes the block (the_post), and then calls Bid.count a second time and compares it to the value it recorded earlier. In this case, since we're looking for Bid.count to change by 1 (positive is implicit here - increase by 1), if that's what happens the matcher stays silent and the example passes.
If the values are the same, or differ by some value other than 1, the example will fail. You can see that work if you change the expectation to by(2) instead of by(1).
HTH,
David
EDIT: you shouldn't expect Bid.count to increment when using a mock object. Mantra I forgot: caffeine before code.
Just commenting out the lines, for now, so the original is still there.
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "POST to bid_controller" do
controller_name :items
before(:each) do
##bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#Bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
# ... more specs
end
Try to write as small specs as possible, write your setences in such a way as to make it obvious what you should be verifying in that spec. For example, how I changed yours from it "should work" to it "should create a new Bid". If there's more to that controller, write a new spec
for each small piece of functionality.
If you do end up needing mock users, there are some helpers for restful_authentication that make it easier. First create a user fixture in
RAILS_ROOT/spec/fixtures/users.yml, like this:
quentin:
login: quentin
email: quentin#example.com
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
created_at: <%= 5.days.ago.to_s :db %>
activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b
activated_at: <%= 5.days.ago.to_s :db %>
name: "Quentin"
Then in your spec you will be able to write the following and have your current_user method and all the other parts of restul_authentication
behave as you would expect them to at runtime.
login_as :quentin
# .... the rest of your spec
As an example of a few more specs I might add as a couple more examples:
def do_post
# extracting the method under test, so I don't repeat myself
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
it "should create a new Bid" do
lambda do
do_post
end.should change(Bid, :count).by(1)
end
it "should assign the Bid to the proper auction" do
#bid.should_receive(:auction_id=).with(1) # test this, I believe doing Bid.new(params[:bid]) sets the id directly not sets the model
do_post
end
it "should assign the Bid the proper points" do
#bid.should_receive(:point=).with(1)
do_post
end
While I don't quite understand what's going on. (with stubs and the lambda)....
for
def bid
#bid = Bid.new params[:bid]
#bid.save
end
The following passes !!
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
fixtures :users
before(:each) do
#user = login_as :quentin
#bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
#Bid.stub!(:save).and_return(true)# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end