You should "test with a purpose" and know why you are testing something and to what level it needs to be tested. An interesting side effect of TDD is that you achieve 100% coverage test – every single line of code is tested – something that traditional testing doesn’t guarantee (although it does recommend it).
In general:
- Tests should be reliable
- Tests should be easy to write
- Tests should be easy to understand
An example of a happy path would be testing that a user gets created correctly. An unhappy path would be testing what happens when a user enters something he/she is not supposed to (e.g. bad email) and how the system handles it.
A unit test should have no dependencies on code outside the unit tested. You decide what the unit is by looking for the smallest testable part. Where there are dependencies they should be replaced by false objects.
An integration test is done to demonstrate that different pieces of the system work together. Integration tests cover whole applications.
Typically Rails users use the RSpec gem to do their testing (as opposed to the default Rails test suite). Common gems that are used in the gemfile are listed below:
group :development, :test do
gem 'rspec-rails' # RSpec is the general testing framework that most Rails users use
gem 'factory_girl_rails' # Ensures that your test suite is always synced with the latest data
# model structure
end
group :test do
gem 'faker' # fakes data very well
gem 'capybara' # mimics a user visiting your site and clicking on stuff
gem 'guard-rspec' # automatically and intelligently launch specs when files are modified
gem 'launchy' # launches the test view in your browser upon failed integration specs
end
'Rspec-rails' and 'factory_girl_rails' are used in both the development and test environments. The remaining gems are only used when you actually run your specs, so they’re not necessary to load in development. This also ensures that gems used solely for generating code or running tests aren’t installed in your production environment when you deploy to your server.
It is highly recommended that you have one factory for each class that provides the simplest set of attributes necessary to create an instance of that class. If you're creating ActiveRecord objects, that means that you should only provide attributes that are required through validations and that do not have defaults. Other factories can be created through inheritance to cover common scenarios for each class.
BAD!
user = User.create(
name: 'Genoveffa',
surname: 'Piccolina',
city: 'Billyville',
birth: '17 Agoust 1982',
active: true
)
GOOD!
user = FactoryGirl.create :user</pre></code>
And in the 'spec/factories/user.rb' file...
<pre><code>require 'faker'
FactoryGirl.define do
factory :user do |f|
f.name { Faker::Name.first_name }
f.surname { Faker::Name.last_name }
f.city { Faker::City.name }
f.birth { Faker::Date.date }
end
end
- describe - this indicates the component that you are testing - RSpec doesn't care what you put here - it is for humans to read; it wraps a set of tests against one functionality
- it - this is for readability as well - helps to spell out what it is your test is testing for; often includes the word 'should' / 'should_not'
- visit - uses Capybara to simulate visiting a particular route in your app
- context - technically just an alias for 'describe'; contextually, we use it to wrap a set of tests against one functionality under the same state.
- let - sets variables - allows for it to be defined outside the actual 'it' block for better readability
- stub - method with canned response
- mock - object with canned response
General layout:
describe <example group> do
it <code example> do
<subject code>
<expectation about subject code>
end
end
Simple usage of checking for content:
require 'spec_helper'
describe "Static pages" do
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
page.should have_content('Sample App')
end
end
end
Slightly longer example:
describe "launch the rocket" do
before(:each) do
#prepare a rocket for all of the tests
@rocket = Rocket.new
end
context "all ready" do
before(:each) do
#under the state of ready
@rocket.ready = true
end
it "launch the rocket" do
@rocket.launch().should be_true
end
end
context "not ready" do
before(:each) do
#under the state of NOT ready
@rocket.ready = false
end
it "does not launch the rocket" do
@rocket.launch().should be_false
end
end
end
Using 'let' and other variables:
describe Card do
subject do
Card.new(card_type)
end
describe "#value" do
context "Two of Hearts" do
let(:card_type) { "2H" }
its(:value) { should == 2 }
end
context "Bad Value" do
it "should raise StandardError" do
expect { card = Card.new("ZZ") }.to raise_error(StandardError)
end
end
end
end
Mock & stub:
x = mock_model(X, :greet => 'hello')
http://rubydoc.info/gems/rspec-core/frames
https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md
http://lmws.net/describe-vs-context-in-rspec
http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks/
https://gist.github.com/enocom/3bd7c6f9e91c6a83f00a