Only one expectation per it {} block: So how to improve this spec? - ruby-on-rails

My Rails models: task has_many positions.
Scenario: When I create a new position, it should create itself a task. I'd like to test that, and I'm doing it like this:
context "creating a new position" do
let(:position) { create :position, name: 'Read some books', :task => nil }
it "should create a simple task" do
Task.find_by_name('Read some books').should be_nil # First should
position # Execute let() block (FactoryGirl is lazy evaluating)
Task.find_by_name('Read some books').should_not be_nil # Second (more relevant) should
end
end
So how should I improve my test? The first "should" simply makes sure that there isn't already a Task, so we can be sure that creating the Position creates the Task. But this violates the "only one should per it block" principle. So what about this?
context "creating a new position" do
let(:position) do
position = create :position, name: 'Read some books', :task => nil
Task.delete_all
position
end
it "should create a simple task" do
position # Execute let() block (FactoryGirl is lazy evaluating)
Task.find_by_name('Read some books').should_not be_nil
end
end
Or should I simply count on the fact that there shouldn't be such a task anyways (because a clean test db wouldn't have one)? Thanks for your opinions.
Update (Solution)
After some research I found the change matcher of RSpec:
let(:position) { create :position, name: 'Read some books', :task => nil }
it "should create a simple task" do
# Thanks to FactoryGirl's lazy evaluation of let(), the position doesn't yet exist in the first place, and then after calling position in the expect{} block, it is created.
expect { position }.to change{ Task.count(conditions: { name: 'Read some books' }) }.by(1)
end

What to Test
I will not address in detail whether the tests themselves are useful to any degree. To me, they seem to be exercising basic database functions rather than application logic, which is of marginal utility, but only you can really decide what's important to test.
Be Specific
In the example you give, there's no real reason to use a let block, which memoizes the variable. If only one test needs the record, instantiate it just in that specific test. For example:
context 'creating a new position' do
it 'should be nil when the position record is missing' do
Task.find_by_name('Read some books').should be_nil
end
it 'should successfully create a position' do
create :position, name: 'Read some books', :task => nil
Task.find_by_name('Read some books').should_not be_nil
end
end
Alternatively, if you're trying to test how your application behaves when a record is missing, then go ahead and memoize a variable or create a record in a before block, but explicitly delete the record in that one specific test.
Multiple Contexts
Finally, if you're finding that you have too much state to set up in individual tests, that's usually a clue that you should consider splitting your tests into different contexts. For example, you might want to separate tests into one context that checks behavior when a record doesn't exist, and a separate context for when records do exist.
Like all things testing, it's an art more than a science. Your mileage may vary.

RSpec 2.11 allows you to pass a block to change, and it expects the return value of the block to be the thing that changes. I would expect this to work for you:
expect { position }.to change { Task.where(:name => 'Read some books').count }.from(0).to(1)

Related

Rspec - Running setup for many examples in one context only once

I have just finished reading Testing Rails guide by thoughtbot. The author mentions that using let! or let is an antipattern because in your test you dont get to see the variables that are needed for the setup.
I have this one common case that I'm curious how to deal with however if lets would be removed. I'm creating tests for creating a cart. When a cart is created, many things are tested. The current test is as follows (I removed the implementation of some methods and just kept the structure for brevity):
RSpec.describe Mutations::Carts::CreateCart, type: :request do
context 'Sending a request with an unauthorized user' do
it 'Creates a new cart and set the table for it' do
# Test setup
store = create :store
branch = store.branches.first
table = create :table, branch: branch
create_dependencies_for_create_cart(store)
cart_count_before_request = Cart.count
# Exercise
send_create_cart(store.id, table.id)
# Verify: (Some expects)
end
it 'Returns the branch id and and the table id' do
# Test setup
store = create :store
branch = store.branches.first
table = create :table, branch: branch
create_dependencies_for_create_cart(store)
# Exercise
send_create_cart(store.id, table.id)
# Verify: Some expects
end
end
private
def send_create_cart(store_id, table_id)
# Prepare and send the request
end
def create_dependencies_for_create_cart(store)
# Creating some objects that need to be created before the cart is created
end
end
My problem here is that the test setup is common and needs to only run once. Also the request can be sent once.
One tiny improvement over this would be to add a before(:each). This would on the one hand DRY the code a bit, but on the other hand, I'm still running the setup + exercise twice potentially slowing my tests down, and I'm making the test less readable. before(:all) would at least ensure that the setup and exercise are only run once, still this is deperacted and recommended against by the Ruby community.
What's the clean and effecient way (both readable and requires no repeated setups) to test such cases?
All create should be set via let. Keep in mind that all your specs must be independent all the time. No matter the order they have been run.
If you've a test that checks if a new cart has be created. That's one test. Another one might be to check the response.
Even if best practices say it's better to have a single expect per spec, sometime it makes sense to have several. It's all about context and logical for other dev to debug your code later on.
Here is my proposition:
context 'Sending a request with an unauthorized user' do
subject { send_create_cart(store.id, table.id) }
let(:store) { create(:store) }
let(:branch) { create(:branch, store: store)} # check according to your association
let!(:table) { create(:table, branch: branch) }
# Create dependencies for cart here, on `let` too.
# use `!` to force creation
# Here only `table` has `!` since other variable are called to create this one.
it 'Creates a new cart and set the table for it' do
expect{ subject }.to change { Cart.count }.by(1)
end
it 'Returns the branch id and and the table id' do
expect(subject).to eq([branch.id, table.id])
end
private
def send_create_cart(store_id, table_id)
# Prepare and send the request
end
end
A good approach would be using a describe with a context with a different describes inside it each having its own before do.
describe 'Sending a request' do
context ' with an unauthorized user' do
store = create :restaurant
branch = store.branches.first
table = create :table, branch: branch
describe 'creating cart and table' do
before do
create_dependencies(store)
cart_count_before_request = Cart.count
# Exercise
send_create_cart()
end
it 'should not create a cart' do
# Verify: (Some expects)
end
it 'returns the ids'do
# Verify: Some expects
end
end
end
end

Rails/RSpec: Memoized/cached block on a context level e.g. before(:context) referencing a let

While writing specs, I often have a expensive operation I'd like to test multiple aspects of without needing to call it each time. How do I do this, while still referencing a greater scoped variable?
describe Hq::UsersController
let(:create_params) {
{'name': 'Bob',
'email': 'bob#burgers.com'}
}
describe "#create" do
context "when a user does not already exist" do
before(:context) do
# (some expensive operation here)
VCR.use_cassette('external_user_check') do
# note: this will error out, see message below
post :create, :params => create_params
end
end
it "creates the user and dependent objects" do
expect(User.count).to be(1)
user = User.first
expect(user.name).to eq(create_params['name'])
end
it "returns an 204 (no content) status" do
expect(response).to have_http_status(:no_content)
end
end
context "when a user already exists" do
before(:context) do
User.create(email: create_params['email'])
end
it "raises a RecordInvalid error and does not create any objects" do
VCR.use_cassette('external_user_check') do
expect { post :create, :params => create_params }.to raise_error(ActiveRecord::RecordInvalid)
end
# I wish I could break this into two `it` blocks :(
expect(User.count).to be(0)
end
end
end
end
This is the error I get:
`let` and `subject` declarations are not intended to be called
in a `before(:context)` hook, as they exist to define state that
is reset between each example, while `before(:context)` exists to
define state that is shared across examples in an example group.
I understand the intent of warning, but in my case, how can we run or cache an expensive operation just one time, while also reusing some shared state across contexts? Generally when I write specs, I assume the state is the same within a context.
Right now, it seems my only solution is to copy and paste the params in multiple context blocks for this to be correct. However, that would costly to maintain if the params change or if the params are lengthy.
I'm open to refactoring suggestions or if this happens to indicative of some spec code smell. Thanks!

why factoryGirl don't rollback after build and save in rspec

I don't understand why my record in 2 spec is not rollback:
I have 2 tables, prefecture has_many hospitals
require "rails_helper"
RSpec.describe Hospital, type: :model, focus: true do
context "created along with new hospital created" do
it "should create new hospital" do
#prefecture = FactoryGirl.create(:prefecture)
hospital = FactoryGirl.build(:hospital, id: 10, prefecture: #prefecture)
expect { hospital.save }.to change { Hospital.count }.by 1
end
it "should save" do
#prefecture = FactoryGirl.create(:prefecture)
hospital = FactoryGirl.build(:hospital, id: 10, prefecture: #prefecture)
hospital.save
end
end
end
If I run it will show error "id=10 is existed in db"
Can anyone explain where I am mistake?
Thank you.
It is not the responsibility of FactoryGirl. You have 2 options:
Use transactions in tests: https://relishapp.com/rspec/rspec-rails/docs/transactions
Use a gem which cleans DB after after test such as: https://github.com/DatabaseCleaner/database_cleaner
Usually your models test will use transctions and database cleaner is used for integration/acceptance tests which cannot operate within transaction because a separate process (browser) needs to read the data created in tests.
By default, the rspec will not reset your database for each test. You need to configure it. Is because that your second test is falling, already exists a Hospital with id = 10.
When you remove the parameter id for the second test and runs with success, FactoryGirl will generate automatically a new id.
The method build from FactoryGirl will not fill the attribute id, just the rest of the attributes. Is because that when you call save after build method, the FactoryGirll generate a new id and your tests pass successfully.
after :build do |offer|
offer.save
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

What's a nice way to verify within a unit test that an ActiveRecord transaction is being used?

I have a class which performs several database operations, and I want to write a unit test which verifies that these operations are all performed within a transaction. What's a nice clean way to do that?
Here's some sample code illustrating the class I'm testing:
class StructureUpdater
def initialize(structure)
#structure = structure
end
def update_structure
SeAccount.transaction do
delete_existing_statistics
delete_existing_structure
add_campaigns
# ... etc
end
end
private
def delete_existing_statistics
# ...
end
def delete_existing_structure
# ...
end
def add_campaigns
# ...
end
end
Rspec lets you assert that data has changed in the scope of a particular block.
it "should delete existing statistics" do
lambda do
#structure_updater.update_structure
end.should change(SeAccount, :count).by(3)
end
...or some such depending on what your schema looks like, etc. Not sure what exactly is going on in delete_existing_statistics so modify the change clause accordingly.
EDIT: Didn't understand the question at first, my apologies. You could try asserting the following to make sure these calls occur in a given order (again, using RSpec):
EDIT: You can't assert an expectation against a transaction in a test that has expectations for calls within that transaction. The closest I could come up with off the cuff was:
describe StructureUpdater do
before(:each) do
#structure_updater = StructureUpdater.new(Structure.new)
end
it "should update the model within a Transaction" do
SeAccount.should_receive(:transaction)
#structure_updater.update_structure
end
it "should do these other things" do
#structure_updater.should_receive(:delete_existing_statistics).ordered
#structure_updater.should_receive(:delete_existing_structure).ordered
#structure_updater.should_receive(:add_campaigns).ordered
#structure_updater.update_structure
end
end
ONE MORE TRY: Another minor hack would be to force one of the later method calls in the transaction block to raise, and assert that nothing has changed in the DB. For instance, assuming Statistic is a model, and delete_existing_statistics would change the count of Statistic in the DB, you could know that call occurred in a transaction if an exception thrown later in the transaction rolled back that change. Something like:
it "should happen in a transaction" do
#structure_updater.stub!(:add_campaigns).and_raise
lambda {#structure_updater.update_structure}.should_not change(Statistic, :count)
end

Resources