Mocha expectation on association build call failing - ruby-on-rails

I have this example:
# GET New
context "on get to new" do
it "should assign cardset" do
#profile.cardsets.expects(:build).once.returns(Factory.stub(:cardset))
get :new
assigns[:cardset].should_not be_nil
end
end
To test this method:
# GET /cardsets/new
def new
#cardset = current_user.cardsets.build
end
I am trying to enforce that the association is built from current_user to make sure the user is only creating things that belong to themselves. I am using an expectation very similarly to ensure they are calling find from the current_user object and it works find, but when running the above example I get:
6)
Mocha::ExpectationError in 'CardsetsController for a logged in user on get to new should assign cardset'
not all expectations were satisfied
unsatisfied expectations:
- expected exactly once, not yet invoked: [#<Cardset:0x102eaa8c8>, #<Cardset:0x102e12438>].build(any_parameters)
satisfied expectations:
- allowed any number of times, not yet invoked: ApplicationController.require_user(any_parameters)
- allowed any number of times, already invoked twice: #<CardsetsController:0x1030849c8>.current_user(any_parameters)
/Applications/MAMP/htdocs/my_app/spec/controllers/cardsets_controller_spec.rb:32:

You add the expectation to #profile after you've stubbed the function that returns it from current_user. Probably what you need to do is this:
# GET New
context "on get to new" do
it "should assign cardset" do
#profile.cardsets.expects(:build).once.returns(Factory.stub(:cardset))
controller.stubs(:current_user).returns(#profile)
get :new
assigns[:cardset].should_not be_nil
end
end

Related

what exactly is this rspec stub :where and what does it do?

I am looking through some tests written by other people and i keep seeing ':where' in their tests. I guess its a stub, but just finding my feet with testing and want to know is it any different from a normal stubs, what does the naming imply?
describe "delete destroy" do
context "is not allowed by user" do
before do
allow(model).to receive(:where).and_return(no_instances)
allow(no_instances).to receive(:first).and_return(no_instances)
end
context "where the record is destroyed" do
before do
allow(instance).to receive(:destroy).and_return(true)
delete :destroy, params_id
end
sets_flash(:notice)
redirects_to('/airbrake_accounts')
end
context "where the record is not destroyed" do
before do
allow(instance).to receive(:destroy).and_return(false)
delete :destroy, params_id
end
sets_flash(:error)
redirects_to('/airbrake_accounts')
end
end
context "where the record is not found" do
before do
allow(model).to receive(:where).and_return(no_instances)
delete :destroy, params_id
end
sets_flash(:error)
redirects_to('/airbrake_accounts')
end
end
I can see what is going on here (I think), things like ':new' are controller actions right?
describe "photo create" do
before do
allow(model).to receive(:new).and_return(instance)
end
context "where all is not well" do
before do
allow(instance).to receive(:save).and_return(false)
post :create, params_new_instance
end
sets_flash(:error)
it "should render the new form" do
expect(response).to render_template("entries/new")
end
end
context "where all is well" do
before do
allow(instance).to receive(:save).and_return(true)
post :create, params_new_photo
end
sets_flash(:notice)
redirects_to ('/photos')
end
end
They are class or instance methods on the model. Lets just say that the model variable in your example is set to the Dog model and this is testing the DogController.
# model
class Dog
def where(params)
do_stuff
end
end
# controller
class DogController > ApplicationController
def destroy
#dogs = Dog.where(id: 1)
redirect :new
end
end
Now I want to test whats going on in my controller, but I don't want to test anything that my model actually does. I'm isolating a unit of my code to be tested. This is different than testing how it all works integrated together (google unit testing or integration testing).
In order to test just whats going on in my controller I stub the methods happening on my model to keep things isolated and clean. The way I do this is by stubbing it out in my controller spec. So In my DogControllerSpec I do:
before do
allow(Dog).to receive(:where).and_return([])
end
So I'm saying allow my dog class to receive the 'where' method call, but don't execute its logic, and instead return me an empty array.
Then I can setup up the code I'm actually testing which is that my destroy method renders new when called.
it "should render the new form" do
expect(response).to render_template("dogs/new")
end
The key here is really that you are attempting to decouple your tests, so that when you unit test your DogController#destroy you are not testing your Dog.where method. The reasoning being that if you change code in your Dog model it should not break specs in your DogControllerSpec.
They are using symbol #to_proc, I believe, to 'stub' calling the method #where on model. So that means the line:
allow(model).to receive(:where).and_return(no_instances)
is essentially
model.where #=> [].
:new is a controller action, but the way this code is using it is more like SomeClass.new, i.e. the method to create an instance of a class(which is the model).

When creating using a custom rspec matcher in and example group, the message is empty

I'm using a custom rspec matcher within a controller spec the message is always empty.
The spec looks like:
describe QuestionnaireController do
matcher :redirect_to_sign_in_if_not_authenticated do |method|
match do |controller|
self.send(method)
response.should redirect_to new_user_session_path
end
end
describe "GET index" do
it { should redirect_to_sign_in_if_not_authenticated(get :index) }
end
end
When running this test, and it fails, all that comes up is:
Failures:
1) QuestionnaireController GET show
As you can see the default should message is missing here. How do I get it to show up?
You can use a failure_message_for_should block, as described here: https://www.relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher#overriding-the-failure-message-for-should
However, you're probably going to run into a few problems here:
get :index will actually call the get method, and then pass the return value to the matcher, which the code does not seem to be expecting.
Errors & backtraces will probably be messed up if you use another matcher (should redirect_to) inside your custom matcher.
You might want to consider a shared example instead: https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples

Rspec testing instance variables with user creation

I'm testing to make sure that a created user is assigned to my instance variable #user. I understand what get means, but I'm not sure what to write for the test. I'm returning with an argument error for a bad URI or URL. What's wrong with my test and how do I fix it?
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :users
# I'm confused on what this line above means/does. What does the hash :users refer
#to
assigns[:user].should == [p]
end
The expected URI object or string error refers to get :users and the error is as follows
Failure/Error get :users
ArgumentError:
bad argument: (expected URI object or URI string)
I guess that what you want is
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :show, id: p.id
assigns(:user).should == p
end
The line you were not sure about checks that content of the assigned variable (#user) in the show view of the user p, is equal to the p user you just created more information there
what action are you trying to test? usually, for creation, you need to test that the controller's "create" action creates a user and assigns an #user variable
I would test it this way:
describe 'POST create' do
it 'creates a user' do
params = {:user => {:name => 'xxx', :lastname => 'yyy'}}
User.should_receive(:create).with(params)
post :create
end
it 'assigns the user to an #user instance variable' do
user = mock(:user)
User.stub!(:create => user)
post :create
assigns(:user).should == user
end
end
notice that I stub/mock all user methods, since you are testing a controller you don't have to really create the user, you only test that the controller calls the desired method, the user creation is tested inside the User model spec
also, I made 2 tests (you should test only 1 thing on each it block if possible, first it test that the controller creates a user, then I test that the controller assigns the variable
I'm assuming your controller is something like this:
controller...
def create
#user = User.create(params[:user])
end
which is TOO simple, I guess you have more code and you should test that code too (validations, redirects, flash messages, etc)

Set current_user in test

I have a test that looks like this:
test "should get create" do
current_user = FactoryGirl.build(:user, email: 'not_saved_email#example.com')
assert_difference('Inquiry.count') do
post :create, FactoryGirl.build(:inquiry)
end
assert_not_nil assigns(:inquiry)
assert_response :redirect
end
That's testing this part of the controller:
def create
#inquiry = Inquiry.new(params[:inquiry])
#inquiry.user_id = current_user.id
if #inquiry.save
flash[:success] = "Inquiry Saved"
redirect_to root_path
else
render 'new'
end
end
and the factory:
FactoryGirl.define do
factory :inquiry do
product_id 2
description 'I have a question about....'
end
end
but I keep getting errors in my tests:
1) Error:
test_should_get_create(InquiriesControllerTest):
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
What am I doing wrong? I need to set the current_user, and I believe I am in the test, but obviously, that's not working.
You didn't create current_user. It was initialized only in test block.
There are two differents ways to do it:
First, use devise test helpers. Something like that
let(:curr_user) { FactoryGirl.create(:user, ...attrs...) }
sign_in curr_user
devise doc
Second, you can stub current_user method in your controllers for test env
controller.stub(current_user: FactroryGirl.create(:user, ...attrs...))
And you should use FactoryGirld.create(...) instead of FactoryGirl.build(...), because you factory objects have to be persisted.(be saved in db and has id attribute not nil)
There are several things which come to mind:
FactoryGirl.build(:user, ...) returns unsaved instance of a user. I'd suggest to use Factory.create instead of it, because with unsaved instance there's no id and there's no way for (usually session based) current_user getter to load it from database. If you're using Devise, you should "sign in" user after creating it. This includes saving record in DB and putting reference to it into session. See devise wiki
Also, passing ActiveRecord object to create action like this looks weird to me:
post :create, FactoryGirl.build(:inquiry)
Maybe there's some rails magic in play which recognizes your intent, but I'd suggest doing it explicitly:
post :create, :inquiry => FactoryGirl.build(:inquiry).attributes
or better yet, decouple it from factory (DRY and aesthetic principles in test code differ from application code):
post :create, :inquiry => {product_id: '2', description: 'I have a question about....'}
This references product with id = 2, unless your DB doesn't have FK reference constraints, product instance may need to be present in DB before action fires.

Rails: RSpec controller test passes without action being implemented, why?

I have the following RSpec example which is passing:
describe UsersController do
it "should render new template on new action" do
get :new
response.should render_template("users/new")
end
end
The problem is I have not implemented the 'new' action on the UsersController.
Can anyone tell me why this test is passing?
I am new to RSpec, so any tips would be greatly appreciated!
When requesting an action for which a view exists, but the action is not defined, Rails will simply render the view. Therefore your spec (correctly) passes.
In addition to this spec, you may want to test for the assignment of particular instance variables. Example:
it "should assign the found articles for the view" do
assigns[:article].should == [#article]
end

Resources