I've got a request spec failing on a model with an association; when I binding.pry into it I can manually create the record with the same parameters, but when I pass them through the post '/path/model' request, it fails and says the association should exist. That's good - I want the association to be required. But the post action seems to be unable to pass.
# job model
belongs_to :worker
validates :title, presence: true
# worker model
has_many :jobs
# jobs controller
# POST /jobs
def create
#job = Job.create!(job_params)
json_response(#job, :created)
end
# jobs request spec
describe "POST /v1/jobs" do
context "when the request is valid" do
before {
post '/v1/jobs', params: {
title: "Whatever",
worker_id: Worker.first.id,
}
}
it "creates a job" do
puts request.body.read
puts response.body
expect(json["title"]).to eq("Whatever")
end
it "returns status code 201" do
expect(response).to have_http_status(201)
end
end
end
These tests both fail. The result of the puts statements above is:
title=Whatever&worker_id=21
{"message":"Validation failed: Worker must exist"}
If I put a binding.pry there instead, the following successfully creates a Job:
Job.create(title: "Whatever", worker_id: Worker.first.id)
My migrations are:
# jobs migration
create_table :jobs do |t|
t.references :worker, index: true
t.text :title
end
# worker migration
create_table :workers do |t|
t.text :first_name
...
end
What am I missing here? I know that the belongs_to association is now non-optional in Rails 5, but again, I want that. So I don't want to flag it optional just to get my tests to pass. Any ideas?
It looks like you haven't created a Worker for the spec to work with. Remember that your database is empty in each spec, so Worker.first.id will fail, because there is no workers in the database.
In your spec you want to create a worker (I use FactoryBot, the same concept applies to fixtures or whatever you're using):
context "when the request is valid" do
let!(:worker) { create :worker }
before {
post '/v1/jobs', params: {
title: "Whatever",
worker_id: worker.id,
}
}
it "creates a job" do
puts request.body.read
puts response.body
expect(json["title"]).to eq("Whatever")
end
it "returns status code 201" do
expect(response).to have_http_status(201)
end
end
I used let! because that will force the record to be created before the spec is run. Now Worker.first.id will find that worker, and you're on your way!
You're correct in that it's caused by belongs_to associations being required by default in Rails 5.
What I usually do is make it optional for creates: belongs_to :worker, optional: :new_record? and then write a test to ensure that it did get created.
Turns out this was a problem in the controller; I'm using strict parameters and didn't have the worker_id in the list of permitted params. Easy to overlook but it's a trap! Hope this helps someone else, especially since all the advice about the belongs_to association for Rails 5 says, "just make it optional."
Related
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.
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 am trying to test a "Post create" action with Rspec. The code is as follows:
def valid_attributes
{
:zone => Flymgr::Zone.new(:countries => Flymgr::ZoneCountry.first,
:name => 'USA',
:description => 'USA Flight',
:zipcodes => ''),
:price => '100.00',
:class => 'first',
}
end
def valid_session
{}
end
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin)
sign_in admin
end
describe "POST create" do
describe "with valid params" do
it "creates a new Flymgr::Rule" do
expect {
post :create, {:Flymgr_rule => valid_attributes}
}.to change(Flymgr::Rule, :count).by(1)
end
One of the required attributes for the form is a 'zone', this is a dropdown box and the options for the dropdown are created with a different form. I do not know how to create an form entry using Rspec. As you can see, I have tried to call a method from a different controller Flymgr::Zone.new. I don't think this is working and it is breaking my test.
Can anyone advise on the best way to do this? Perhaps I should be using FactoryGirl to create a zone and rule entry?
your request parameter hash has an object as value of :zone, when you post it will just be 'to_s'-ed, which is unlikely what you want.
In general the best practice is to use factory girl to build your objects and use the attributes_for strategy to parameterize its attributes for the post request:
What is the proper way to test 'create' controller actions?
Your question is suggesting that the association is a belong_to so you just need to post an id. Be aware that at present, FactoryGirl does not create any attributes for the associations. If your factory definition for rule takes care of the zone association, you can use this workaround:
FactoryGirl.build(:flymgr_rule).attributes
to also include a zone_id but, then you need to exclude the unwanted params.
("id", "created_at", "updated_at", etc).
So you may be better off explicitly insert the params hash info for zone the way you see it in a valid post request.
Read this thread on factorygirl attributes and associations:
https://github.com/thoughtbot/factory_girl/issues/359
As the guide points out:
# Returns a hash of attributes that can be used to build a User instance
attrs = FactoryGirl.attributes_for(:user)
Rails 3.0.3....
I'm just starting out with Factory Girl, having had little success with the standard fixtures approach. I've commented out fixtures :all from the test/test_helper.rb file and have created a factories file.
My problem is that the sequence feature doesn't seem to work:
# test/factories.rb
Factory.sequence :clearer_name do |n|
"Clearer_#{n}"
end
Factory.define :clearer do |f|
f.name Factory.next(:clearer_name)
end
My (functional) test is only slightly different from standard:
require 'test_helper'
class ClearersControllerTest < ActionController::TestCase
setup do
#clearer = Factory.create(:clearer)
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:clearers)
end
test "should get new" do
get :new
assert_response :success
end
test "should create clearer" do
assert_difference('Clearer.count') do
post :create, :clearer => #clearer.attributes
end
assert_redirected_to clearer_path(assigns(:clearer))
end
When I run rake test I get:
test_should_create_clearer(ClearersControllerTest):
ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: column name is not unique: INSERT INTO "clearers" ("active", "updated_at", "name", "created_at") VALUES ('t', '2011-02-20 08:53:37.040200', 'Clearer_1', '2011-02-20 08:53:37.040200')
...as if it's not continuing the sequence.
Any tips?
Thanks,
UPDATE: heres my test file:
#clearers_controller_test.rb
require 'test_helper'
class ClearersControllerTest < ActionController::TestCase
setup do
#clearer = Factory.create(:clearer)
end
test "should create clearer" do
assert_difference('Clearer.count') do
# does not work without this:
Clearer.destroy_all
post :create, :clearer => #clearer.attributes
end
end
I can get this to work by putting Clearer.destroy_all at the top of the test method as shown, but that doesn't feel right.
I see - In your setup, you're creating a Clearer instance. The Factory.create method builds and saves a new record and returns it.
The problem is that you're then attempting to create another instance in your "should create clearer" test but you're re-using the existing instance's attributes.
If you want Factory to return fresh attributes (and the next name sequence), you need to ask it for new attributes:
test "should create clearer" do
assert_difference('Clearer.count') do
post :create, :clearer => Factory.attributes_for(:clearer)
end
end
You should only be using that existing #clearer instance in the context of an existing record, not where you want a new one.
You aren't starting with a fresh database is my guess. There's lots of reasons this could be happen, but you can verify that's the problem by adding a Clearer.destroy_all in your setup function, before creating one.
Sequences and other attribute values that are calculated at runtime need to be procs, not static values.
Change:
Factory.define :clearer do |f|
f.name Factory.next(:clearer_name)
end
to:
Factory.define :clearer do |f|
f.name {Factory.next(:clearer_name)}
end
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