Should A Custom Method Be Used In The before(:each) Section - ruby-on-rails

I have a method on the model Category called create_main used to create main categories. Should I use this method in the before(:each) section even though the method itself has to be tested, or should the main category be created manually using rails built in functionality.

It should be possible to partition your Examples into two Example Groups, one group where before(:each) is called with create_main, and you use this to test everything except create_main. Then, you have another subset, where before(:each) does not call create_main, and here you test create_main.
In your case, I think you could try something like the following:
describe Category, " without a main category" do
before(:each) do
# No call to create_main here
end
it "should create the main category" do
# Here we test that create_main is working
end
end
describe Category, " with a main category already created" do
before(:each) do
# This time, we do call create_main to set up the object as necessary
end
# More examples go here that depend on create_main
end
Give that a shot. I'm not 100% sure it works, but I've seen similar setups in the past.

Related

Assigns has been extracted to a gem. Use gem 'rails-controller-testing'. Is there an alternative for the gem?

I am writing some tests for a controller tasks, the index action, which has an instance variable #tasks with all the tasks (Task.all).
If I follow the official documentation:
RSpec.describe TeamsController do
describe "GET index" do
it "assigns #teams" do
team = Team.create
get :index
expect(assigns(:teams)).to eq([team])
end
it "renders the index template" do
get :index
expect(response).to render_template("index")
end
end
end
The assigns method is moved to the gem file 'rails-controller-testing'.
I have two questions:
1 - How can I achieve the same as expect(assigns(:teams)).to eq([team]). I guess I am asking, how can I check if I have an instance variable in the index action with values [team]
2 - If this method was moved to the gem, I read in the Github issues, that the reason is: You shouldn't test it there, controller should just test response, cookies etc. But I am confuse, since in relish you can test the instance variable. Should I test it there or not? If not, where? In my views/index_spec.rb, testing if I have all the teams?
3 - Alternative: Since TeamsController is a normal class, should I create a spec in the spec/models/folder spec/models/tasks_controller.rb and there test if the method index has the instance variable #teams with the content that I want?
Thanks
The whole idea is that instead of poking inside your controller and testing its internal variables is flawed you should instead test your controllers by testing the output.
In RSpec you can do this with request and feature specs.
# config/specs/features/teams_spec.html
RSpec.feature 'Teams' do
scenario 'when a user views the teams' do
Team.create(name: 'Team Rocket')
visit '/teams'
expect(page).to have_content 'Team Rocket'
end
end
# config/specs/requests/teams_spec.html
RSpec.describe 'Teams', type: :request do
describe 'GET /teams.json' do
it "includes the team" do
team = Team.create(name: 'Team Rocket')
get teams_path(format: :json)
expect(parsed_response.first['name']).to eq 'Team Rocket'
end
end
describe 'GET /teams' do
it "includes the team" do
team = Team.create(name: 'Team Rocket')
get teams_path
expect(page).to have_content 'Team Rocket'
end
end
end
The key difference is that feature specs test the app from a user story POV by driving a browser simulator while request specs are lighter weight and you just test against the raw response.
1 - How can I achieve the same as expect(assigns(:teams)).to
eq([team]). I guess I am asking, how can I check if I have an instance
variable in the index action with values [team]
Either use the assigns gem for legacy compatiblity or test the rendered output.
2 - If this method was moved to the gem, I read in the Github issues,
that the reason is: You shouldn't test it there, controller should
just test response, cookies etc. But I am confuse, since in relish you
can test the instance variable. Should I test it there or not? If not,
where? In my views/index_spec.rb, testing if I have all the teams?
If by Relish you mean RSpec, then its been taking a while for RSpec-rails to catch up to the state-of-art in Rails testing. But the same still applies. The offical recommendation of the RSpec team is to not use assigns and faze out controller specs in favor of request specs. View specs are not really relevant here - they are used if you want to test complex views in isolation.
3 - Alternative: Since TeamsController is a normal class, should I
create a spec in the spec/models/folder
spec/models/tasks_controller.rb and there test if the method index has
the instance variable #teams with the content that I want?
Just no. Controllers are not just normal classes. You can't just instantiate a controller with MyController.new, thats why controller tests have all that stubbing in place.

How to test correlated (coupled) methods in RSpec?

Let's say we have class:
class Post
def save
# implementation
end
def self.find(id)
#implementation
end
end
I struggle with testing #save and .find, I've:
describe '#save' do
it 'saves the post' do
subject.save
created = Post.find(subject.id)
expect(created).to eq(subject)
end
end
describe '.find' do
it 'finds the post' do
subject.save
created = Post.find(subject.id)
expect(created).to eq(subject)
end
end
In case of #save method I'd like to check side effect, in case of .find I'd like to test returned value. How to cope with this case without duplicating specs ?
In this case, to isolate the save and find actions, you need to mock the repository.
Whether you are writing to a DB, a file-system, cache, or whatever - you can mock it to either expect the saving feature, or set it up (before the beginning of the test) to make sure find works.
For most repository implementations there are gems to mock them (Factory Girl for relational databases, FakeFS for file-system), but you can roll your own if you have some exotic repository no one has heard of.
This way you test save without using find, or vice versa.

Stubbing out ActiveRecord models in Service tests

I'm following a TDD approach to building our app, and creating a whole bunch of service objects, keeping models strictly for data management.
Many of the services I've built interface with models. Take for example MakePrintsForRunner:
class MakePrintsForRunner
def initialize(runner)
#runner = runner
end
def from_run_report(run_report)
run_report.photos.each do |photo|
Print.create(photo: photo, subject: #runner)
end
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Now, in the spec for MakePrintsForRunner I'm keen to avoid including spec_helper, since I want my service specs to be super fast.
Instead, I stub out the Print class like this:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
class Print; end
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
And all goes green. Perfect!
... Not so fast. When I run the whole test suite, depending on the seed order, I am now running into problems.
It appears that the class Print; end line can sometimes overwrite print.rb's definition of Print (which obviously inherits from ActiveRecord) and therefore fail a bunch of tests at various points in the suite. One example is:
NoMethodError:
undefined method 'reflect_on_association' for Print:Class
This makes for an unhappy suite.
Any advice on how to tackle this. While this is one example, there are numerous times where a service is directly referencing a model's method, and I've taken the above approach to stubbing them out. Is there a better way?
You don't have to create the Print class, simply use the one that is loaded, and stub it:
describe RunnerPhotos do
let(:runner) { double }
let(:photo_1) { double(id: 1) }
let(:photo_2) { double(id: 2) }
let(:run_report) { double(photos: [photo_1, photo_2]) }
before(:each) do
#service = RunnerPhotos.new(runner)
end
describe "#create_print_from_run_report(run_report)" do
before(:each) do
allow(Print).to receive(:create)
#service.create_print_from_run_report(run_report)
end
it "creates a print for every run report photo associating it with the runners" do
expect(Print).to have_received(:create).with(photo: photo_1, subject: runner)
expect(Print).to have_received(:create).with(photo: photo_2, subject: runner)
end
end
end
Edit
If you really need to create the class in the scope of this test alone, you can undefine it at the end of the test (from How to undefine class in Ruby?):
before(:all) do
unless Object.constants.include?(:Print)
class TempPrint; end
Print = TempPrint
end
end
after(:all) do
if Object.constants.include?(:TempPrint)
Object.send(:remove_const, :Print)
end
end
I appreciate the create method could arguably be abstracted into the Print model, but let's keep it as is for now.
Let's see what happens if we ignore this line.
Your difficulty in stubbing a class is a sign that the design is inflexible. Consider passing an already-instantiated object to either the constructor of MakePrintsForRunner or the method #from_run_report. Which to choose depends on the permanence of the object - will the configuration of printing need to change at run time? If not, pass to the constructor, if so, pass to the method.
So for our step 1:
class MakePrintsForRunner
def initialize(runner, printer)
#runner = runner
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo: photo, subject: #runner)
end
end
end
Now it's interesting that we're passing two objects to the constructor, yet #runner is only ever passed to the #print method of #printer. This could be a sign that #runner doesn't belong here at all:
class MakePrints
def initialize(printer)
#printer = printer
end
def from_run_report(run_report)
run_report.photos.each do |photo|
#printer.print(photo)
end
end
end
We've simplified MakePrintsForRunner into MakePrints. This only takes a printer at construction time, and a report at method invocation time. The complexity of which runner to use is now the responsibility of the new 'printer' role.
Note that the printer is a role, not necessarily a single class. You can swap the implementation for different printing strategies.
Testing should now be simpler:
photo1 = double('photo')
photo2 = double('photo')
run_report = double('run report', photos: [photo1, photo2])
printer = double('printer')
action = MakePrints.new(printer)
allow(printer).to receive(:print)
action.from_run_report(run_report)
expect(printer).to have_received(:print).with(photo1)
expect(printer).to have_received(:print).with(photo2)
These changes might not suit your domain. Perhaps a runner shouldn't be attached to a printer for more than one print. In this case, perhaps you should take a different next step.
Another future refactoring might be for #from_run_report to become #from_photos, since the report isn't used for anything but gathering photos. At this point the class looks a bit anaemic, and might disappear altogether (eaching over photos and calling #print isn't too interesting).
Now, how to test a printer? Integrate with ActiveRecord. This is your adapter to the outside world, and as such should be integration tested. If all it really does is create a record, I probably wouldn't even bother testing it - it's just a wrapper around an ActiveRecord call.
Class names are just constants so you could use stub_const to stub an undefined constant and return a double.
So instead of defining a class in your before(:each) block do this:
before(:each) do
stub_const('Print', double(create: nil))
#service.create_print_from_run_report(run_report)
end

Best practice for reusing code in Rspec?

I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end

rspec testing for content

I'm using Rspec to test the contents of a view in my Controller spec. I'm trying to test that all Product entries have their descriptions displayed on the page.
describe StoreController do
render_views
describe "GET 'index'" do
before(:each) do
get :index
end
it "should display the product list" do
Product.all.each do |product|
response.should have_selector("p", :content => product.description)
end
end
end
end
This doesn't seem to work, however, as the test passes regardless of what's in the view. Still very new to Rails, so it's probable that I'm doing something completely wrong here. How can I make the code test for the presence of each product description in the StoreController index view?
Personally I wouldn't test the contents of the view in the controller. I'd just test the outgoing output of the controller actions and any support methods. I'd put view related tests into the view specs.
If you look in the ones that are generated by rails you should see some examples of how to assert content there.
I figured it out. The problem was that my test database was not populated with any Products, so the each block was never executing. Fixed it by using Factory Girl to make sure there was data in the test database beforehand.

Resources