I've encountered problem using RSpec and decent_exposure gem in my Rails application.
My controller tests are failing, because of decent_exposure calls method "new" twice (Model.new(params[name]). Once with name (Brand.new(params["brands"]) returning Brand.new(nil)) and second what I expect (Brand.new(params["brand"])). I need somehow skip first call in my test file. Brand.should_receive(:new).with(...).once.and_return(band) is not working.
My test file:
let(:brand) {
mock_model(Brand).as_null_object
}
before do
Brand.stub(:new).and_return(brand)
end
describe "with valid parameters" do
it "should create a new brand" do
Brand.should_receive(:new).with(
"name" => "LG",
).and_return(brand)
post :create, :brand => {
"name" => "LG",
}
end
end
So, can you please help me figure out how to get pass this?
Try this:
Brand.should_receive(:new).once.with(any_args())
Brand.should_receive(:new).once.with("name" => "LG").and_return(brand)
I'd advise adding an expectation for whatever method the controller uses to persist brand. Usually this is save:
brand.should_receive(:save) { true }
Related
I am new to Rspec. I am writing a test case to cover some action in a model. Here is my rspec code
test_cover_image_spec.rb
require 'spec_helper'
describe Issue do
before :each do
#issue = Issue.joins(:multimedia).uniq.first
binding.pry
end
describe '#release_cover_image' do
context 'While making an issue open' do
it 'should make issue cover in S3 accessible' do
put :update, :id => #issue.id, :issue => #issue.attributes = {:open => '1'}
end
end
end
end
#issue always returns nil. In my debugger also, Issue.all returns an empty array.
Tests usually run in isolation. That means each test needs to set up the objects before running. After the test run common test configurations delete all created data from the test database. That means you need to create your test data before you can use it.
For example like this:
require 'spec_helper'
describe Issue do
# pass all attributes to create a valid issue
let(:issue) { Issue.create(title: 'Foo Bar') }
describe '#release_cover_image' do
context 'While making an issue open' do
it 'should make issue cover in S3 accessible' do
put :update, id: issue.id, issue: { open: '1' }
expect(issue.reload.open).to eq('1')
end
end
end
end
To make this work you have to populate the test database first.
Check out factory_girl gem - it is most often used for easy generating test data.
So (general idea is that) you will have to create few factories:
issues_factory.rb
multimedia_factory.rb
And use them, to generate the issue object prior the test run.
If you're not going to use factory_girl then anyway you should change from creating an issue in before block to using let:
let(:issue) { Issue.create }
I'm newbie with rspec and I'm facing some problems with it. Could someone help me?
I have a controller action responsible for deactivate an user. I'm trying to cover it with rspec tests, but the result is not what I'm waiting for.
Controller:
def deactivate
#user = User.find(params[:id])
if !#user.nil?
#user.update_attribute(:active, false)
redirect_to users_url
end
end
Controller Spec
describe "PUT #deactivate" do
describe "with valid parameters" do
before (:each) do
#user = mock_model(User, :id => 100, :login => "login", :password => "password123",
:email => "email#gmail.com", :active => true)
User.should_receive(:find).with("100").and_return(#user)
end
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.active.should eq false
end
end
end
The test result:
1) UsersController PUT #deactivate with valid parameters should deactivate an user
Failure/Error: #user.active.should eq false
expected: false
got: true
(compared using ==)
So, I don't understand why the active attribute stills true when it should be false. Any ideas ?
Thanks!
You appear to be stubbing the update_attribute method unnecessarily. Try removing that line and see what happens.
I look for this for a long time, update_column can always work no matter you use let or build
Your expectation is "wrong".
Let's see what happens when your spec it "should deactivate an user" is executed:
#user.stub!(:update_attribute).with(:active, false).and_return(true) modifies the existing mock model, so it has an update_attribute which, when called with arguments :active and false
will return true
will keep track that this call has happened (that's what mocks do)
(and, unlike a real User object, will do nothing else)
put :deactivate, :id => "100" calls the real deactivate in your Controller
Your Controller calls User.find. But you've mocked that class method, which will return the mock object #user instead of searching for the actual user with that id.
Your Controller calls #user.update_attribute. But because of step 3 above, #user here is the mock object, too. Its update_attributes method is the one from step 1. As we've seen above, it will return true, keep track that this call happened and do nothing else. Which means it will not change #user's active attribute, so that stays true.
Changing active when update_attribute is called is functionality of objects of the actual User class, but no such object came into play while running your spec. Because this functionality is inherited from ActiveRecord, you don't have to test it. Instead just test that the update_attribute has been received by the mock object:
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.should have_received(:update_attribute).with(:active, false)
end
(I'm guessing about the old should syntax here, based on how it's done with the newer expect syntax.)
To mock or not?
If you do want to test the combined functionality of your controller with the actual User implementation, do not mock User or its objects. Instead test from the browser perspective with a request spec. (It might make sense to do that additionally, even if you want the isolated tests for only controller (with model mocked) and for only model (which probably won't require doubles, except maybe for other models).
Can you try this:
describe "should deactivate an user" do
before do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
end
it { #user.active.should eq false }
end
when you are mocking the call to update_attribute, how is the model going to change?
if you are a beginner: DONT use stubs and mocks!
first get a general knowledge in testing, THEN expand your knowledge to mocks and stubs.
Here is my Lesson model:
before_create :set_sequence
def set_sequence
maxseq = Lesson.where(:course_id => self.course_id).maximum("sequence")
if (maxseq.nil?)
maxseq = 0
end
self.sequence = maxseq + 1
end
when I run rspec the following test fails:
it "validate sequence is setup" do
lesson = Lesson.create(:title => "Testing", :description => "Testing", :course_id => 1)
lesson.sequence.should_not eql nil
end
However when T test this through rails console the Lesson object is created successfully and with the correct sequence. Any ideas why?
lesson.sequence.should_not be_nil is the correct way to test for nil, as far as I know. Have you tried that?
Any validations you've got on Lesson could be silently aborting the create before your callback gets called. Change it to create! in the spec to check it.
FactoryGirl first initializes object with no parameters, and then assigns parameters one by one. The callback in your model probably would not work in this case. So you can try to change FactoryGirl's behavior by adding
initialize_with { new(attributes) }
to the Lesson's factory. I'm not sure it will help though. Testing callback behavior in Rails is tricky.
I'm a little confused about what is going on with the scaffold controller specs that rspec generates. It seemed to be making sense until I added authorization to my app and now I need to update my tests.
MyClass.stub(:new).with('these' => 'params') { mock_my_class(:save => true) }
In my controller I merge a hash into params when creating a new record (it needs the current_user id to be valid). MyClass.new(params[:my_class].merge(:user_id => current_user.id))
Test Fails
expected: ({"these"=>"params"})
got: ({"these"=>"params", "user_id"=>315})
It makes sense that the test fails because the new method receives params it didn't expect. It expected to receive {'these' => 'params'} but it actually received {'these' => 'params', 'user_id' => 1234}
So my natural reaction is to adjust the test because the new method should receive {'these' => 'params', 'user_id' => 1234} and return the mock object.
So I add to the test as follows:
MyClass.stub(:new).with({'these' => 'params', 'user_id' => #user.id}) { mock_my_class(:save => true) }
Here is where I get thrown through a loop. The output of the test is as follows:
expected: ({"these"=>"params", "user_id"=>298})
got: ({"these"=>"params"})
It seems as if a successful test is magically evading me. I'm sure there is a logical reason for these results, but I can't seem to figure them out.
Any help? :)
note:
The rspec site says the following:
Account.should_receive(:find).with("37").and_return(account)
or
Account.stub!(:find).and_return(account)
This is easy enough to follow it just seems odd the the scaffold generated would not contain these methods (unless I botched something which is possible (: )
Passes
login_admin
describe "with valid params" do
it "assigns a newly created forum_sub_topic as #forum_sub_topic" do
ForumSubTopic.stub(:new) { mock_forum_sub_topic(:save => true) }
ForumSubTopic.should_receive(:new).with({"these"=>"params", "user_id"=> #admin.id}) #PASS!
post :create, :forum_sub_topic => {'these' => 'params'}
assigns(:forum_sub_topic).should be(mock_forum_sub_topic) #PASS!
end
end
Fails
login_admin
describe "with valid params" do
it "assigns a newly created forum_sub_topic as #forum_sub_topic" do
ForumSubTopic.stub(:new).with({'these' => 'params', 'user_id' => #user.id}) { mock_forum_sub_topic(:save => true) }
post :create, :forum_sub_topic => {'these' => 'params'}
assigns(:forum_sub_topic).should be(mock_forum_sub_topic)
end
end
"Never trust a junkie", as the saying goes. One could also say, "never trust a scaffold".
OK, that's being a little bit too harsh. The scaffold does its best to figure out which parameters will work for the models/controllers you are generating, but it doesn't know about nested resources (which is what I assume you are using), so it won't generate the user_id in the params hash. Add that:
post :create, :forum_sub_topic => {:user_id=>#user.id}
The these_params key is generated as an example — remove it and add whatever parameters are needed for the controller to create a MyClass.
Regarding the with option: stub and should_receive will only stub out messages that meet the specified conditions, i.e. if you do:
MyClass.stub(:new) {mock_model(MyClass,:save=>true)}
Then MyClass will respond to any new message with the mock. If, on the other hand, you do:
MyClass.stub(:new).with({:bogus=>37}) {mock_model(MyClass,:save=>true)}
Then MyClass will only respond to new when it also receives {:bogus=>37} as an argument.
I have a rails controller, defined here:
https://github.com/abonec/Simple-Store/blob/master/app/controllers/carts_controller.rb
On the cart page a user can specify the quantity of line_items by posting nested attributes. The parameters look like this:
{ "cart" => {
"line_items_attributes" => {
"0" => {
"quantity" => "2",
"id" => "36" } } },
"commit" => "Update Cart",
"authenticity_token" => "UdtQ+lchSKaHHkN2E1bEX00KcdGIekGjzGKgKfH05So=",
"utf8"=>"\342\234\223" }
In my controller action these params are saved like this:
#cart.update_attributes(params[:cart])
But I don't know how to test this behavior in a test. #cart.attributes only generates model attributes not nested attributes.
How can I test this behavior? How to simulate post request with nested attributes in my functional tests?
A little late to the party, but you shouldn't be testing that behavior from the controller. Nested attributes is model behavior. The controller just passes anything to the model. In your controller example, there is no mention of any nested attributes. You want to test for the existence of the behavior created by accepts_nested_attributes_for in your model
You can test this with rSpec like this:
it "should accept nested attributes for units" do
expect {
Cart.update_attributes(:cart => {:line_items_attributes=>{'0'=>{'quantity'=>2, 'other_attr'=>"value"}})
}.to change { LineItems.count }.by(1)
end
Assuming you're using Test::Unit, and you have a cart in #cart in the setup, try something like this in your update test:
cart_attributes = #cart.attributes
line_items_attributes = #cart.line_items.map(&:attributes)
cart_attributes[:line_items] = line_items_attributes
put :update, :id => #cart.to_param, :cart => cart_attributes
Using test/unit in Rails3, first generate a integration test:
rails g integration_test cart_flows_test
in the generated file you include you test, something like:
test "if it adds line_item through the cart" do
line_items_before = LineItem.all
# don't forget to sign in some user or you can be redirected to login page
post_via_redirect '/carts', :cart => {:line_items_attributes=>{'0'=>{'quantity'=>2, 'other_attr'=>"value"}}}
assert_template 'show'
assert_equal line_items_before+1, LineItem.all
end
I hope that helped.
After you update the cart with the nested attributes, you can access the nested attributes by doing
#cart.line_items