I was just starting out with RSpec, and im trying to do something quite trivial, but I couldnt find any good documentation on a good way of doing this. I want to test a sequence of events, say the friendship between two users.
My spec is:
describe User do
describe "friendships" do
describe ".friends?" do
before do
#user1 = FactoryGirl.build(:user)
#user2 = FactoryGirl.build(:user)
end
it "should be false for non friends" do
#user1.friends?(#user2).should be false
#user2.friends?(#user1).should be false
end
it "should be false for requested friendship" do
Friendship.create(:user_id => #user1.id, :friend_id => #user2.id) # 1
#user1.friends?(#user2).should be false
#user2.friends?(#user1).should be false
end
it "should be true for accepted friendship" do
Friendship.for_users(#user1, #user2).update_attribute(:approved, true) # 2
#user1.friends?(#user2).should be true
#user2.friends?(#user1).should be true
end
end
end
end
I am creating a friendship at the line marked # 1, and expect it to be present at line # 2, but I am guessing the database gets flushed in between the two.
Is this the wrong way of testing such a flow of events? What should I be doing? Any pointers would be much appreciated.
Yes, database gets flushed between the flow of events.
The best approach to testing is to have each test set up what it needs, and throw it away when it's done. That way each test is completely self contained, so you don't have to depend on previous tests for your later ones.
You may consider just building the friendship in factory girl and then testing that the friendship exists, and then make people friends through the UI in an integration test and check the same thing
I guess you are looking for context. Something like this:
context "when friends" do
before { Friendship.create ... }
it "should be false for requested friendship"
it "should be true for accepted friendship"
end
Sorry to say that but you should probably take a look on rspec docs: basic structure (describe/it)
Related
How to create multiple scenarii without to have to reinit context between 2 tests with RSpec ? I don't need context reinitialisation (that is very slow), but I need to check multiple things for the same given context. The example below works, but it's a bad example : the context is reinitialized. If I do before(:all), it doesnt works because of the stubs. Any idea ?
feature 'Aids page' do
context 'No active user' do
before(:each) do
create_2_different_aids
disable_http_service
visit aids_path
end
after(:each) do
enable_http_service
end
scenario 'Should display 2 aids NOT related to any eligibility' do
display_2_aids_unrelated_to_eligibility
end
scenario 'Should not display breadcrumb' do
expect(page).not_to have_css('.c-breadcrumb')
end
end
end
feature specs often have more than one expect in the same scenario. They are not like unit tests where each it should test only one thing... they are more like what a user actually does on a page: "go here, click on this thing, check I can see that thing, click there, check that the thing changes" etc... so you can just do something like this:
feature 'Aids page' do
context 'No active user' do
scenario 'Sees aids not related to eligibility' do
create_2_different_aids
disable_http_service
visit aids_path
expect(page).not_to have_css('.c-breadcrumb')
display_2_aids_unrelated_to_eligibility
enable_http_service
end
end
end
Alternatively... it's possible to use either shared setup (as you have already done). That is fairly common.
Found. Actually there is a hack you can use to initialize your test only once, as the example shown below :
feature 'Aides page' do
context 'No active user' do
that = nil
before do
if !that
create_2_different_aids
disable_http_service
visit aides_path
that = Nokogiri::HTML(page.html)
end
end
after do
enable_http_service
end
scenario 'Should display 2 aids NOT related to any eligibility' do
display_2_aids_unrelated_to_eligibility(that)
end
scenario 'Should not display breadcrumb' do
expect(that.css('.c-breadcrumb').size).to eq(0)
end
end
end
I have been using RSpec and Cucumber for a few months. But since I'm the only developer here, it's been all self learning, so I'm asking this to clarify where to test what.
I'm currently creating a CMS for Coupons. There's a form to create a new coupon. I have the happy path working and tested in Cucumber. Should I also have a test for when the form is filled out incorrectly? If so, should I be creating a scenario for each case that doesn't pass my validations?
I already have my validations tested in RSpec:
it { should validate_presence_of(:name) }
it { should validate_presence_of(:code) }
describe "validations" do
specify "the start date must be before the end date" do
site_wide_coupon = SiteWideCoupon.new(name: "Free Shipping", code: "ABC123")
site_wide_coupon.start_date = 1.month.from_now
site_wide_coupon.end_date = 1.month.ago
expect(site_wide_coupon.valid?).to be_false
expect(site_wide_coupon.errors.full_messages).to include("Start date must be before the end date")
end
context "it is a flat amount coupon" do
let(:site_wide_coupon) {
site_wide_coupon = SiteWideCoupon.new(name: "Flat Amount", code: "ABC123")
site_wide_coupon.start_date = 1.month.ago
site_wide_coupon.end_date = 1.month.from_now
site_wide_coupon.valid?
site_wide_coupon
}
it "validates presence of discount_amount" do
expect(site_wide_coupon.errors.full_messages).to include("Discount amount can't be blank")
end
it "doesn't validate the presence of discount_percentage" do
expect(site_wide_coupon.errors.full_messages).not_to include("Discount percentage can't be blank")
end
end
context "it is a percentage amount coupon" do
let(:site_wide_coupon) {
site_wide_coupon = SiteWideCoupon.new(name: "Percentage Amount", code: "ABC123")
site_wide_coupon.start_date = 1.month.ago
site_wide_coupon.end_date = 1.month.from_now
site_wide_coupon.valid?
site_wide_coupon
}
it "validates presence of discount_amount" do
expect(site_wide_coupon.errors.full_messages).to include("Discount percentage can't be blank")
end
it "doesn't validate the presence of discount_percentage" do
expect(site_wide_coupon.errors.full_messages).not_to include("Discount amount can't be blank")
end
end
end
describe "#flat_amount?" do
context "name equals 'Flat Amount'" do
it "returns true" do
site_wide_coupon = SiteWideCoupon.new(name: "Flat Amount")
expect(site_wide_coupon.flat_amount?).to be_true
end
end
context "name doesn't equal 'Flat Amount'" do
it "returns false" do
site_wide_coupon = SiteWideCoupon.new(name: "Something else")
expect(site_wide_coupon.flat_amount?).to be_false
end
end
end
describe "#percentage_amount?" do
context "name equals 'Percentage Amount'" do
it "returns true" do
site_wide_coupon = SiteWideCoupon.new(name: "Percentage Amount")
expect(site_wide_coupon.flat_amount?).to be_true
end
end
context "name doesn't equal 'Flat Amount'" do
it "returns false" do
site_wide_coupon = SiteWideCoupon.new(name: "Something else")
expect(site_wide_coupon.flat_amount?).to be_false
end
end
end
Would it be necessary to test that my validations fire in Cucumber then? Or would that just be repeating my RSpec tests and not adding any value?
Or maybe I should only have one test that submits the form without filling anything in and testing that the errors show up on the page?
What do you guys usually do?
My two cents: Forget about cucumber. Having to keep another whole DSL in your head and maintain the steps results in a lot of wasted time in the long run (especially if you're working by yourself). RSpec is a much better way to go as it's closer to plain Ruby, and you can probably even transition to MiniTest::Spec fairly easily. Don't erase your cucumber tests all at once, but delete them as they fail and replace them with specs.
As for duplication in tests, I tend to not mind it as much. Since you're defining detailed scenarios and their expected outcomes, a repetitive series of tests can be more explicit and easier to understand than even a well-refactored suite.
It comes down to why you're testing in the first place. The goal of a test suite is to warn developers in the event that something in broken so that they can fix it. Imo double testing is a form of waste since the goal of the test suite has been fulfilled if after writing one test.
So long story short I reckon it's fine to test it just in rSpec unless your cucumber test is testing something different.
I think that having validation tests only in RSpec is not sufficient. If you want to make sure that user can see the error message you have to use Cucumber/Capybara/Jasmine...
I agree that testing all validations in Cucumber is not a good idea.
However testing one validation per form might be really helpful.
My approach is that I test each validation in RSpec + one Cucumber scenario per form with failing one validation.
I have a still pretty simple Rails application that I want to develop using BDD with Cucumber and TDD with RSpec. Currently, I am hanging at a test where I want to check that if a new instance of an Organizer (that's the model I have) cannot be created due to a validation error. I would like to check that the errors Array of the object to be created is not empty so that I can be sure that error messages are available for showing them in the view.
require 'spec_helper'
describe OrganizersController do
render_views
describe "POST 'create'" do
describe "with invalid arguments" do
before(:each) do
request.env["HTTP_REFERER"] = organizers_new_path
#organizer_args = { :name => "" }
end
it "should return a non-empty list of errors" do
post 'create', :organizer => #organizer_args
#organizer.errors.empty?.should_not be_true
end
end
end
end
I am developing based on Rails 3.2.9 with RSpec 2 and cucumber-rails.
Any suggestions are appreciated. Thanks!
You should use assigns method to get instance variable from controller action:
assigns(:organizer).errors.empty?.should_not be_true
The latest preferred syntax is:
expect(assigns(:organizer).errors.empty?).to_not be_true
thanks for the answer guys but I'd like to suggest a slightly nicer syntax:
expect(assigns(:organizer).errors).to_not be_empty
(unrelated to the question 👇)
Basically whenever you have a method that ends with ? you'll have the corresponding rspec matcher that starts with be_ e.g.
1.odd? #=> true
expect(1).to be_odd
I'm newbie with rspec and I'm facing some problems with it. Could someone help me?
I have a controller action responsible for deactivate an user. I'm trying to cover it with rspec tests, but the result is not what I'm waiting for.
Controller:
def deactivate
#user = User.find(params[:id])
if !#user.nil?
#user.update_attribute(:active, false)
redirect_to users_url
end
end
Controller Spec
describe "PUT #deactivate" do
describe "with valid parameters" do
before (:each) do
#user = mock_model(User, :id => 100, :login => "login", :password => "password123",
:email => "email#gmail.com", :active => true)
User.should_receive(:find).with("100").and_return(#user)
end
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.active.should eq false
end
end
end
The test result:
1) UsersController PUT #deactivate with valid parameters should deactivate an user
Failure/Error: #user.active.should eq false
expected: false
got: true
(compared using ==)
So, I don't understand why the active attribute stills true when it should be false. Any ideas ?
Thanks!
You appear to be stubbing the update_attribute method unnecessarily. Try removing that line and see what happens.
I look for this for a long time, update_column can always work no matter you use let or build
Your expectation is "wrong".
Let's see what happens when your spec it "should deactivate an user" is executed:
#user.stub!(:update_attribute).with(:active, false).and_return(true) modifies the existing mock model, so it has an update_attribute which, when called with arguments :active and false
will return true
will keep track that this call has happened (that's what mocks do)
(and, unlike a real User object, will do nothing else)
put :deactivate, :id => "100" calls the real deactivate in your Controller
Your Controller calls User.find. But you've mocked that class method, which will return the mock object #user instead of searching for the actual user with that id.
Your Controller calls #user.update_attribute. But because of step 3 above, #user here is the mock object, too. Its update_attributes method is the one from step 1. As we've seen above, it will return true, keep track that this call happened and do nothing else. Which means it will not change #user's active attribute, so that stays true.
Changing active when update_attribute is called is functionality of objects of the actual User class, but no such object came into play while running your spec. Because this functionality is inherited from ActiveRecord, you don't have to test it. Instead just test that the update_attribute has been received by the mock object:
it "should deactivate an user" do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
#user.should have_received(:update_attribute).with(:active, false)
end
(I'm guessing about the old should syntax here, based on how it's done with the newer expect syntax.)
To mock or not?
If you do want to test the combined functionality of your controller with the actual User implementation, do not mock User or its objects. Instead test from the browser perspective with a request spec. (It might make sense to do that additionally, even if you want the isolated tests for only controller (with model mocked) and for only model (which probably won't require doubles, except maybe for other models).
Can you try this:
describe "should deactivate an user" do
before do
#user.stub!(:update_attribute).with(:active, false).and_return(true)
put :deactivate, :id => "100"
end
it { #user.active.should eq false }
end
when you are mocking the call to update_attribute, how is the model going to change?
if you are a beginner: DONT use stubs and mocks!
first get a general knowledge in testing, THEN expand your knowledge to mocks and stubs.
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