I am trying to test the new action in my controller. At the moment it looks like this:
Controller
def new
#business = Business.new
#business.addresses.build
end
Spec
describe 'GET #new' do
it 'assigns a new business to #business' do
get :new
expect(assigns(:business)).to be_a_new(Business)
end
end
I would like to test the line '#business.addresses.build'. How do I do this?
Thanks in advance!
How about
expect(assigns(:business).addresses.first).to be_a_new(Address)
Assuming build is a method, you only need test to ensure that build is called. You could do so by replacing the new Business with a mock that has an addresses attribute that expects to receive :build.
I haven't tested this, but I suspect you could do something like:
business = double('business')
addresses = double('addresses')
business.should_receive(:addresses).and_return(addresses)
addresses.should_receive(:build)
Business.stub(:new).and_return(business)
Related
I am trying to write two RSpec tests for two different problems that are much more advanced that what I'm used to writing.
What I'm trying to test within my controller:
def index
#buildings ||= building_class.active.where(place: current_place)
end
My attempt at writing the RSpec test:
describe 'GET :index' do
it "assigns #buildings" do
#buildings ||= building_class.active.where(place: current_place)
get :index
expect(assigns(:buildings)).to eq([building])
end
end
This test failed and wouldn't even run so I know I'm missing something.
My second test is needing to test the returned value of a class method. Here is what I am needing to test within the controller:
def class_name
ABC::Accountant::Business
end
Here is my attempt at testing this method:
describe "class name returns ABC::Accountant::Business" do
subject do
expect(subject.class_name).to eq(ABC::Accountant::Business)
end
end
For the first test I would do something like this:
First, I would move that .active.where(place: current_place) to a scope (I'm guessing building_class returns Building or something like that):
class Building << ApplicationRecord
scope :active_in, -> (place) { active.where(place: place)
Then it's easier to stub for the test
describe 'GET :index' do
it "assigns #buildings" do
scoped_buildings = double(:buildings)
expect(Building).to receive(:active_in).and_return(scoped_buildings)
get :index
expect(assigns(:buildings)).to eq(scoped_buildings)
end
end
Then your controller will do
#buildings ||= building_class.active_in(current_place)
This way you are testing two things: that the controller actually calls the scope and that the controller assigns the returned value on the #buildings variable (you don't really need to test the actual buidlings, you can test the scope on the model spec).
Personally, I feel like it would be better to do something like #buildings = current_place.active_buildings using the same idea of the scope to test that you are getting the active buildings of the current place.
EDIT: if you can't modify your controller, then the stubbing is a little different and it implies some chaining of methods that I don't like to explicitly test.
scoped_buildings = double(:buildings)
controller.stub_chain(:building_class, :active, :where).and_return(scoped_building)
get :index
expect(assings(:buildings)).to eq scoped_buildings
Note that now your test depends on a specific implementation and testing implementation is a bad practice, one should test behaviour and not implementation.
For the second, I guess something like this should work:
describe ".class_name" do
it "returns ABC::Accountant::Business" do
expect(controller.class_name).to eq(ABC::Accountant::Business)
end
end
IMHO, that the method's name if confusing, class_name gives the idea that it returns a string, you are not returnin a name, you are returning a class. Maybe you can change that method to resource_class or something less confusing.
I have extracted part of my Foos controller into a new rails model to perform the action:
foos_controller.rb
class FoosController < ApplicationController
respond_to :js
def create
#foo = current_user.do_something(#bar)
actioned_bar = ActionedBar.new(#bar)
actioned_bar.create
respond_with #bar
end
actioned_bar.rb
class ActionedBar
def initialize(bar)
#bar = bar
end
def create
if #bar.check?
# do something
end
end
end
I got it working first but now I'm trying to back-fill the rspec controller tests.
I'll be testing the various model methods and will be doing a feature test to make sure it's ok from that point of view but I would like to add a test to make sure the new actioned_bar model is called from the foos controller with #bar.
I know in rspec you can test that something receives something with some arguments but I'm struggling to get this to work.
it "calls ActionedBar.new(bar)" do
bar = create(:bar)
expect(ActionedBar).to receive(:new)
xhr :post, :create, bar_id: bar.id
end
This doesn't work though, the console reports:
NoMethodError:
undefined method `create' for nil:NilClass
which is strange because it only does this when I use expect(ActionedBar).to receive(:new), the rest of the controller tests work fine.
If I try to do:
it "calls ActionedBar.new(bar)" do
bar = create(:bar)
actioned_bar = ActionedBar.new(bar)
expect(actioned_bar).to receive(:create).with(no_args)
xhr :post, :create, bar_id: bar.id
end
the console says:
(#<ActionedBar:0xc8f9f74>).create(no args)
expected: 1 time with no arguments
received: 0 times with no arguments
If I do a put in the controller whilst running the test; for some reason this test causes the actioned_bar in the controller to be output as nil but fine for all the other controller tests.
Is there any way I can test that ActionedBar is being called in this controller spec?
You can use expect_any_instance_of(ActionedBar).to receive(:create), because instance in spec and in controller are different instances.
If you want to use original object, you can use expect(ActionedBar).to receive(:new).and_call_original (without that #new just will return nil and you'll get NoMethodError).
You can set up a double ActionedBar which is returned by the ActionedBar.new call as this instance is different to the one used in the controller.
describe "#create" do
let(:actioned_bar) { double(ActionedBar) }
let(:bar) { double(Bar) }
it "calls ActionedBar.new(bar)" do
expect(ActionedBar).to receive(:new).with(bar).and_returns(actioned_bar)
expect(actioned_bar).to receive(:create)
xhr :post, :create, bar_id: bar.id
end
end
The core problem is that actioned_bar in your spec in is not going to be the the same instance of ActionedBar that is in your controller. Thus the spec will always fail.
Instead you need to have ActionedBar return a double when new is called:
it "calls ActionedBar.new(bar)" do
bar = create(:bar)
actioned_bar = instance_double("ActionedBar")
allow(ActionedBar).to receive(:new).and_return(actioned_bar)
expect(actioned_bar).to receive(:create).with(no_args)
xhr :post, :create, bar_id: bar.id
end
However I generally consider this kind of test a code smell - its ok to mock out external collaborators and set expectations that you are passing the correct messages. But your might want to consider if your are testing the details of how your controller does its job and not the actual behavior.
I find it better to setup a spec which calls the controller action and set expectations on what how it for example changes the database state or how it effects the response.
In a rails (4.2.x) app, in controller tests, I see a lot of examples where they use assigns(:post), where I could use Post.first or Post.find_by(title: 'foo') instead. I don't understand the need for assigns at all. (I heard that assigns will be deprecated in rails 5, but until then, is it ok to use the object directly?) Is it wrong to do this, for example:
assert_redirected_to(post_path(Post.first))
instead of:
assert_redirected_to(post_path(assigns(:post))
assigns(:post) is just a refer to an action variable #post.
Action can look like:
#post = Post.first
or:
#post = Post.find_by(title: 'foo')
In test through assigns(:post) you can get a #post variable from the controller action.
Using Post.first is possible, but couples your test to the database implementation. You're testing controller functionality, so you can stub out the call to the db and speed up your test.
Stubbing out this call will look something like:
before { allow(Post).to_receive(:find){ build :post } }
In this case, your controller specs will continue to work if you check assigns(:post), but they will break if you check Post.first
I am in the process of refactoring a bloated controller that serves a polymorphic model for carousels. I am trying to build a class method that handles finding and returning the item that is carouselable.
In my RSPEC tests I want to stub the method, 'is_something?' on the venue that is found as a result of the params.
def self.find_carouselable(params)
.......
elsif params[:venue_id].present?
venue=Venue.friendly.find(params[:venue_id])
if venue.is_something?
do this
else
do that
end
end
end
I cant work out how to stub an object that is created as a result of the inputted data - I am not sure if this is called stubbing or mocking?
context "carouselable is a venue" do
before do
allow(the_venue).to receive(:is_something?).and_return(true)
end
it "returns the instance of the carouselable object" do
expect(CopperBoxCarouselItem.find_carouselable(venue_params)).to eq the_venue
end
end
many thanks
You should be able to do:
allow_any_instance_of(Venue).to receive(:is_something?).and_return(true)
https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/allow-a-message-on-any-instance-of-a-class
You only need to stub the Venue bit, like so
before do
allow(Venue).to receive(:friendly).and_return(some_venues)
allow(some_venues).to receive(:find).and_return(venue)
allow(venue).to receive(:is_something?).and_return(true)
end
How should I spec this nested build:
#projects_controller.rb
def new
#account.projects.build
end
So far I have something like this:
#projects_controller_spec.rb
describe ProectssController do
describe "GET new" do
let(:account) { mock_model(Account) }
let(:project) { mock_model(Project).as_null_object }
before do
Account.stub(:find_by_subdomain!).and_return(account)
#Project.should_receive(:build).with(:account_id => account.id).and_return(project)
end
it "assigns #project" do
get :new
assigns[:project].should eq(project)
end
end
end
Not sure how I should be specing this...
The assignment is missing in ProjectsController#new. Should be:
def new
# ...
#project = #account.projects.build
# ...
end
Then you can stub returning a double as you intended:
it "assigns #project" do
account = mock_model(Account)
Account.stub(:find_by_subdomain!).and_return(account)
project = account.stub_chain(:projects,:build) { mock_model(Project) }
get :new
assigns(:project).should == project
end
In general, I recommend stubbing and mocking as little as possible. I recommend using something like Factory Girl to create real database objects for the tests to interact with. That means that Account and Project would be real ActiveRecord classes, and then #account would be a real AR object with a projects association that works just like it does in production. This is important, since otherwise you're just testing to the implementation you've written, and haven't actually tested that your code functions when it's actually using ActiveRecord.
Once you can do that, I would recommend simply checking things you care about for the Project model, e.g.:
assigns[:project].should be_instance_of(Project)
assigns[:project].should be_new_record
assigns[:project].account.should == logged_in_user
Hope this helps!