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.
Related
Consider this example, we'd like to check whether it's allowed for user to have blank name (it shouldn't be):
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
The question is why does assert_not exists? Wouldn't it be better if we just use assert like this:
test "name should be present" do
#user.name = " "
assert !#user.valid?
end
It likely exists to remove modifiers from your assertions, which may change their results or obscure what you're actually asking. In reality, it's mostly a style choice.
It's kind of the same motivation for having unless in the language, instead of writing this:
if !#user.valid?
# do stuff
end
You would do:
unless #user.valid?
# do stuff
end
Granted, the if/unless differences read way better than assert_not, alas that's what Minitest unit tests are going to get you. If you want things to read more naturally, take a look at Minitest specs or RSpec.
I recently started learning RoR and TDD, and am having trouble figuring out the best way to handle this scenario.
I have an ActiveRecord model with two fields which share the same validations.
How do I write an RSpec test which utilizes the same tests for the similar fields?
"shared examples" looked like a promising feature to utilize in this scenario, but does not seem to work, as I need to test the entire model but am only passing the individual field to the shared example.
Below is my failed attempt:
describe Trip do
before do
#trip = trip.new(date: '2013-07-01', city_1: "PORTLAND",
city_2: "BOSTON")
end
subject { #trip }
shared_examples "a city" do
describe "when not uppercase" do
before { city = city.downcase }
it { should_not be_valid }
end
end
describe "city_1 must be valid" do
it_should_behave_like "a city" do
let!(:city) { #trip.city_1}
end
end
describe "city_2 must be valid" do
it_behaves_like "a city" do
let!(:city) { #trip.city_2}
end
end
end
This fails because updating the city variable does not update trip model. Is there a way to dynamically tie it back to the model?
BTW, all the tests work on their own if I paste under each field. It just will not work in the context of the shared_example.
Any guidance would be greatly appreciated.
You can perform the assertion within a loop, and use a little metaprogramming, but I would advise against it. Tests should be as simple and straightforward as possible, even if that means having a little duplication. If only two fields are involved, just repeat it.
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 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)
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