#Test Driven Development
- Write test cases before you write the code
###Test frameworks
- UnitTest vs. RSpec
- three phases: given , when, then
gem 'rspec-rails'
- run bundle install for the gem above and run:
bin/rails generate rspec:install
- create database
bin/rake db:create RAILS_ENV=test
- run generators
bin/rails g
- in rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'
- combination of unit test => integration testing
bin/rails g rspec:model campaign
- in spec/model/campaign_spec.rb
require 'rails_helper'
RSpec.describe Campaign, type: :model do
# pending "add some examples to (or delete) #{__FILE__}"
describe "validations" do
# it is a method that
# takes a test example description and
# a block of code where you can construct your test
# every test scenario must be put with in an 'it' or 'specify' block
it "doesn't allow creating a compain with no name" do
# Given: compain with no title
c = Campaign.new
# When: we validate the campaign
campaign_valid = c.valid?
#Then: expect that campaign_valid = false
expect(campaign_valid).to eq(false)
end
it "requires a goal" do
#GIVEN: campaign with no goal
c = Campaign.new
# When
c.valid?
# Then
expect(c.errors).to have_key(:goal)
# we call methods like: have_key matchers
# RSpec rails comes with many built-in matchers
end
end
end
- in command
bin/rails g model campaign name description:text goal:integer end_date:datetime
bin/rake db:migrate RAILS_ENV=test
- finally running the test
rspec
- command line
bin/rails g controller campaigns
- create action and template files for new action
- routes
resources :campaigns, only: [:new]
- shell
rspec spec/controllers/campaigns_controller_spec.rb:6
- rails/controller/campaigns_controller
require 'rails_helper'
RSpec.describe CampaignsController, type: :controller do
describe "#new" do
it "renders the new template" do
get :new
# response is an object that is given to us by RSpec that will help test
# things like redirect / render
# render_template is a an RSpec (rspec-rails) matcher that help us check
# if the controller renders the template with the given name.
expect(response).to render_template(:new)
end
end
end
- another campaign controller test. in controller
def new
@campaign = Campaign.new
end
- in campaign_controller_spec.rb
it "instantiates a new Campaign object and sets it to @campaign" do
get :new
# be_a_new is called a matcher
expect(assigns(:campaign)).to be_a_new(Campaign)
end
- and finally
rspec spec/controllers/campaigns_controller_spec.rb:14
- another: create. controller:
def create
render nothing: true
end
- controller_spec
describe "#create" do
context "with valid attributes" do
it "creates a record in the database" do
campaign_count_before = Campaign.count
post :create, campaign: {name: "some valid name",
description: "some valid description",
goal: 1000000
}
campaign_count_after = Campaign.count
expect(campaign_count_after - campaign_count_before).to eq(1)
end
it "redirects to the campaign show page"
it "sets a flash notice message"
end
context "with invalid attributes" do
it "doesn't create a record in the database"
it "renders the new template"
it "sets a flash alert message"
end
end
- gem 'factory_girl_rails'
- in shell, run
bin/rails g factory_girl:model campaign
- make sure in rails_helper.rb, the following is present in factory girl config
config.include FactoryGirl::Syntax::Methods
- this will generate the spec/factories/campaigns.rb file. we can give it some valid attributes:
# key thing to remember is that factories should always provide a set of valid attributes
# which means the factoires should always create record with no validation errors
FactoryGirl.define do
factory :campaign do
# put values you want to assign as a variable,
# then put values you want the factory to assign to that attr
# if yu give value without a block, it's going to be the same for all
# with block, faker will try to generate different values everytime
# to be 100% sure that the values are always unique, use 'sequence'
sequence(:name) {|n| "#{Faker::Company.bs}-#{n}"}
sequence(:description) {|n| "#{Faker::Lorem.paragraph}-#{n}"}
goal 100000
end_date 60.days.from_now
end
end
- then go to bin/rails c, use factory to generate file
bin/rails c
> FactoryGirl.create(:campaign)
(0.2ms) BEGIN
Campaign Exists (0.3ms) SELECT 1 AS one FROM "campaigns" WHERE "campaigns"."name" = 'harness world-class content' LIMIT 1
SQL (0.4ms) INSERT INTO "campaigns" ("name", "description", "goal", "end_date", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["name", "harness world-class content"], ["description", "Et facilis nam officiis. Earum provident tempore quia aut dolore assumenda aspernatur. Voluptatem voluptatem quaerat atque aut ab."], ["goal", 100000], ["end_date", "2016-04-05 18:21:53.063636"], ["created_at", "2016-02-05 18:22:00.020702"], ["updated_at", "2016-02-05 18:22:00.020702"]]
(1.3ms) COMMIT
+----+------------+------------+--------+------------+------------+------------+
| id | name | descrip... | goal | end_date | created_at | updated_at |
+----+------------+------------+--------+------------+------------+------------+
| 3 | extend... | Et faci... | 100000 | 2016-04... | 2016-02... | 2016-02... |
+----+------------+------------+--------+------------+------------+------------+
1 row in set
2.2.3 :004 > _.name
"extend scalable markets-1"
- use attributes_for from Factory to view attribute values
FactoryGirl.attributes_for(:campaign)
{
:name => "incentivize transparent synergies-1",
:description => "Mollitia illum ab. Quia sit reprehenderit quaerat sit placeat modi nisi. Facere praesentium rerum explicabo ut laboriosam. Non consequatur pariatur. Ab eos quidem earum consequuntur.-1",
:goal => 100000,
:end_date => Tue, 05 Apr 2016 18:33:18 UTC +00:00
}
- let
let (:campaign) {FactoryGirl.create(:campaign)}
# def campaign
# @campaign ||= FactoryGirl.create(:campaign)
#
- testing update is a bit tricky because you need to run reload on the record after you update/PATCH it
it "updates the record with new parameters" do
# Given: use the campaign method we defined with let
patch :update, id: campaign.id, campaign: {name: "new valid name"}
# we must use campaign.reload in this scenario because the controller will
# instantiate another campaign object based on the id but it will
# live in another memory location. which means "campaign' here will still
# have the old data not the updated one. reload will make active record rerun
# the qury and feches the information from the DB again
expect(campaign.reload.name).to eq("new valid name")
end
describe "#destroy" do
# let!(:campaign) { FactoryGirl.create(:campaign) }
it "removes the campaign from the database" do
# campaign
# expect { delete :destroy, id: campaign.id }.to change { Campaign.count }.by(-1)
campaign
count_before = Campaign.count
delete :destroy, id:campaign.id
expect(count_before - Campaign.count).to eq(1)
end
it "redirects to the campaign index page" do
delete :destroy, id:campaign.id
expect(response).to redirect_to(campaigns_path)
end
it "displays a notice" do
delete :destroy, id:campaign.id
expect(flash[:notice]).to be
end
end
- Drop and recreate test db
rake db:test:prepare