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
Related
I'm running a rspec test to make sure that two models are associated between each other with has_many and belongs_to. Here is my test below.
describe "testing for has many links" do
before do
#post = Post.new(day: "Day 1", content: "Test")
#link = Link.new(post_id: #post.id, title: "google", url: "google.com")
end
it "in the post model" do
#post.links.first.url.should == "google.com"
end
end
The test is telling me that url is an undefined method. What's wrong with my test? Or did I just miss something basic.
The model file for Post
has_many :links
The model file for Link
belongs_to :post
On top of that, the link model has the attribute post_id
You need to save both models to validate this relationship, also, you can use shoulda gem.
The code looks like:
describe Link do
it { should belong_to(:post) }
end
describe Post do
it { should have_many(:links) }
end
You need to assign your link to your post otherwise, if you do #post.links, you will get a empty array ([]), which [].first returns nil. Then your try nil.url and then you get url is an undefined method for NilClass.
#post = Post.new(day: "Day 1", content: "Test")
#link = Link.new(title: "google", url: "google.com")
#post.links << #link
I'm new to Rails testing, I have this question:
Let's suppose this simple scenario where I have in my controller:
class UsersController < ApplicationController
def create
#user = User.create(params[:user])
if params[:user][:birth_date]
Birthday.create(:title => "#{user.first_name} #{user.last_name}'s Birthday!", :date => params[:user][:birth_date].to_date, :user_id => #user.id)
end
end
def update
#user = User.find(params[:id])
#user.update_attributes(params[:user])
if params[:user][:birth_date]
#birthday = Birthday.find_by_user_id(#user.id)
#birthday.update_attributes(:date => params[:user][:birth_date].to_date) if #birthday
end
end
end
I want to test that every time a user is created the birthday event is created and that it's attributes are properly set. In my particular (real) case I have that a new object is created (or updated) when another object is created (or updated), and a lot of attributes are calculated and automatically set. I need to test that they are set correctly. How can I test it?
Test the Correct Objects
You want to test the object that is populating your User data, or possibly the callbacks in your User model. This is not generally a controller concern, especially if you follow the "fat model, skinny controller" paradigm.
In your specific case, your controller is calling Birthday#update_attributes, but the real changes are happening elsewhere, so that's where I'd test them. The only really useful tests for this particular controller would be ensuring that nothing is raised when you create or update your model data, but that's more of an integration test than a unit test.
If you are using Test::Unit or RSpec you can access the global variables through the "assigns" method. As an example, you could use something like this:
post :update, :id => 1, :user => {:birthdate => '1/1/2000'}
assert_equal Date.new(2000, 1, 1), assigns(:user).birthday.date
You can also test that the database was updated correctly:
user = User.find(1)
assert_equal Date.new(2000, 1, 1), user.birthday.date
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 }
I am building a Ruby on Rails app with the usual assortment of models, views and controllers.
The 'create' action in one of my controllers is supposed to create an instance of two different models. Here's my code:
def create
#league = League.new(params[:league])
#user = #league.users.build(params[:user])
... .save conditions appended ...
end
So, when you call 'create' through the LeaguesController via a POST request to /leagues, you get a new instance of League and a new instance of User. I also want the new User instance to inherit the ID of the new League instance, so it can be used as the foreign key to link the instances together. This is accomplished with:
def create
#league = League.new(params[:league])
#user = #league.users.build(params[:user])
#league_id = #league.id
#user.update_attribute('league_id', #league_id)
... .save conditions appended ...
end
The User model already belongs_to the League model, which has_many users.
The above code works just fine and dandy, verified via manual testing. However, I can't for the life of me figure out how to automate these tests with Rspec. I'm trying to be a good boy and use test-driven design, but this has me stumped.
The issue is that I can't figure out how to access the attributes of the newly created instances of League and User in my tests. I am attempting to do so using the following code:
describe LeaguesController do
describe 'new league and user' do
it 'should create a new user with a league_id equal to the new leagues id'
#league_attr = { :name => "myleague", :confirmation_code => "mycode", :id => 5}
#user_attr = { :username => "myname", :password => "mypass"}
post :create, :user => #user_attr, :league => #league_attr
assigns(:league_id).should eql(5)
end
end
end
But the test returns nil for the value of :league_id
I'm new to both programming in general and Rspec in particular, so I really appreciate any help someone might offer!
You cannot assign :id with new. Try this:
def create
#league = League.new(params[:league])
#league.id = params[:league][:id] if params[:league][:id]
#user = #league.users.build(params[:user])
#league_id = #league.id
#user.update_attribute('league_id', #league_id)
... .save conditions appended ...
end
That said, I wonder how come it works in the browser.
Also, you better off using FactoryGirl or Fixtures to assign data to models when testing.
Have you tried pry?
Whenever I discover something like this I find it very handy to be able to insert a brakepoint via Pry (or Ruby-Debug) so I can inspect the variables and their behavior.
I suspect putting in a binding.pry between #league_id = #league.id and #user.update_attribute('league_id', #league_id) may very well shed some light on the issue.
Also note that user will automatically inherit the #league_id when you persist it via the #league.save call. (that's the idea behind #league.users.build(..) - it will set the required relationships correctly upon persistance.
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.