Rails Testing For Zombies

11 downloads 27310 Views 6MB Size Report
Fail messages for AssertioNs boolean_test.rb ... 4 tests, 4 assertions, 0 failures, 0 errors, 0 skips ... rails generate scaffold zombie name:string graveyard:string.
Test UNit - Level 1 -

TestiNg Philosophy Strict Test Driven Design Test First Verification Testing No Testing

TEST UNIT

what is UNit TestiNg? Test individual parts in isolation Easy to debug Documentation

TEST UNIT

Why Test::UNit? ★ Rails

uses Test::Unit

★ Similar

to other testing libraries

t o b l a T l e i n a h t a N TEST UNIT

Basic Test::UNIt File Structure _test.rb

Included with Ruby

require "test/unit" class Test < Test::Unit::TestCase def test_ end end

Typically one assertion per test TEST UNIT

A passiNg BooleaN Test boolean_test.rb require "test/unit" class BooleanTest < Test::Unit::TestCase def test_true_is_true assert true end end $ ruby boolean_test.rb Loaded suite boolean_test Started . Finished in 0.000389 seconds.

Passed!

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

A Failing BooleaN Test boolean_test.rb class BooleanTest < Test::Unit::TestCase def test_true_is_true assert false end end $ ruby boolean_test.rb Loaded suite boolean_test Started F Finished in 0.000550 seconds.

Failed!

1) Failure: test_true_is_true(BooleanTest) [boolean_test.rb:5]: Failed assertion, no message given.

Not Descriptive

1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

TEST UNIT

Fail messages for AssertioNs boolean_test.rb class BooleanTest < Test::Unit::TestCase def test_true_is_true assert false, "True should be truthy" end end $ ruby boolean_test.rb Loaded suite boolean_test Started F Finished in 0.000550 seconds.

Descriptive!

1) Failure: test_true_is_true(BooleanTest) [boolean_test.rb:5]: True should be truthy 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

TEST UNIT

Test First Example string_extension_test.rb class StringExtensionTest < Test::Unit::TestCase def test_is_number assert "3".is_number? end def test_is_not_number assert !"Blah".is_number? end end

TEST UNIT

RuN the Tests $ ruby string_extension_test.rb Loaded suite string_test Started EE Finished in 0.000817 seconds. 1) Error: test_is_not_number(StringExtensionTest): NoMethodError: undefined method `is_number?' for "Blah":String string_test.rb:39:in `test_is_not_number' 2) Error: test_is_number(StringExtensionTest): NoMethodError: undefined method `is_number?' for "3":String string_test.rb:33:in `test_is_number' 2 tests, 0 assertions, 0 failures, 2 errors, 0 skips

TEST UNIT

Write the Code to make tests pass string_extension.rb class String def is_number? if self =~ /^\d+$/ true else false end end end

string_extension_test.rb assert "3".is_number? assert !"Blah".is_number?

TEST UNIT

RuN the Tests string_extension_test.rb require "test/unit" require "string_extension" ...

Specify Load Path

$ ruby -I. string_extension_test.rb Loaded suite string_extension_test Started .. Finished in 0.000624 seconds.

2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

string_extension.rb class String def is_number? if self =~ /^\d+$/ true else false end end end

string_extension_test.rb assert "3".is_number? assert !"Blah".is_number?

TEST UNIT

string_extension.rb class String def is_number? self =~ /^\d+$/ end end

then rerun tests!

Red, GreeN, Refactor $ ruby -I. string_extension_test.rb Loaded suite string_extension_test Started .. Finished in 0.000624 seconds. 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

RED GREEN REFACTOR TEST UNIT

Let’s add a humaNize Method ★

Should lowercase and capitalize

string_extension_test.rb def test_humanize_function_added_to_string assert_respond_to "blah", :humanize end

string_extension.rb class String def humanize end end

object method

3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

assert_Not_Nil string_extension_test.rb def test_humanize_returns_something assert_not_nil "Yo".humanize, "humanize is returning nil" end

string_extension.rb class String def humanize "Yo" end end

optional error message

4 tests, 4 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

assert_equal string_extension_test.rb def test_humanize assert_equal "Likes me brains!", "LIKES ME BRAINS!".humanize end

expected

string_extension.rb

actual

class String def humanize self.downcase.capitalize end end 5 tests, 5 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

assert_Match string_extension_test.rb def test_just_for_brains assert_match /brains/, "LIKES ME BRAINS!".humanize end

regex

string_extension.rb

string

class String def humanize self.downcase.capitalize end end 6 tests, 6 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

assert_raise string_extension_test.rb def test_zombie_in_humanize_raises_error assert_raise(RuntimeError) { "zombie".humanize } end

string_extension.rb class String def humanize if self =~ /zombie/ raise RuntimeError else self.downcase.capitalize end end end 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips

TEST UNIT

AssertioNs assert assert_equal ,

assert_not_equal

assert_respond_to , : assert_nil

assert_not_nil

assert_match ,

assert_no_match

assert_raise() { }

+ optional error message

assert_kind_of(, )

TEST UNIT

Model testing - Level 2 -

GeNeratiNg Rails Tests $ rails generate scaffold zombie name:string graveyard:string ... invoke test_unit create test/unit/zombie_test.rb create test/fixtures/zombies.yml

/test/unit/zombie_test.rb require 'test_helper'

test configuration

class ZombieTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end

No Tests

MODEL TESTING

Rails ENviroNmeNts /config/database.yml development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000

only for testing

test: adapter: sqlite3 database: db/test.sqlite3 pool: 5 timeout: 5000 production: ...

MODEL TESTING

Rails Rake Tasks $ rake db:test:prepare

check for migrations + load schema

$ rake test

run db:test:prepare + run all tests

$ rake

To run an individual test $ ruby -Itest test/unit/zombie_test.rb

needed to find test_helper

To run a single test case $ ruby -Itest test/unit/zombie_test.rb -n test_the_truth

MODEL TESTING

Our first model test /test/unit/zombie_test.rb require 'test_helper' class ZombieTest < ActiveSupport::TestCase def test_invalid_without_a_name z = Zombie.new assert !z.valid?, "Name is not being validated" end end

MODEL TESTING

Improved test defiNitioN /test/unit/zombie_test.rb require 'test_helper' class ZombieTest < ActiveSupport::TestCase test "invalid without a name" do z = Zombie.new assert !z.valid?, "Name is not being validated" end end $ rake You have 1 pending migrations: 20120114152945 CreateZombies Run "rake db:migrate" to update your database then try again.

Whoops need to migrate!

MODEL TESTING

model test iN the Red run migrations

$ rake db:migrate == CreateZombies: migrating ================================================== -- create_table(:zombies) -> 0.0012s == CreateZombies: migrated (0.0013s) =========================================

$ rake 1) Failure: test_invalid_without_a_name(ZombieTest) [test/unit/zombie_test.rb:6]: Name is not being validated 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

MODEL TESTING

TurNiNg it GreeN /test/unit/zombie_test.rb require 'test_helper' class ZombieTest < ActiveSupport::TestCase test "invalid without a name" do z = Zombie.new assert !z.valid?, "Name is not being validated" end end

/app/models/zombie.rb class Zombie < ActiveRecord::Base validates :name, presence: true end 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

MODEL TESTING

Our secoNd Validity Test /test/unit/zombie_test.rb require 'test_helper' class ZombieTest < ActiveSupport::TestCase test "valid with all attributes" do z = Zombie.new z.name = 'Ash' z.graveyard = 'Oak Park' assert z.valid?, 'Zombie was not valid' end end

Needs Fixture MODEL TESTING

Fixtures /test/fixtures/zombies.yml one: name: MyString graveyard: MyString

ash: name: Ash graveyard: Oak Park

two: name: MyString graveyard: MyString

tim: name: Tim graveyard: Green Meadows

z = Zombie.new z.name = 'Ash' z.graveyard = 'Oak Park'

MODEL TESTING

pulls model from db z = zombies(:ash)

Loaded into database on start + transactional

Test with Fixture /test/unit/zombie_test.rb require 'test_helper' class ZombieTest < ActiveSupport::TestCase test "valid with all attributes" do z = zombies(:ash) assert z.valid?, "Zombie was not valid" end test "invalid name gives error message" do z = zombies(:ash) z.name = nil z.valid? assert_match /can't be blank/, z.errors[:name].join, "Presence error not found on zombie" end end

MODEL TESTING

New Method /test/unit/zombie_test.rb test "can generate avatar_url" do z = zombies(:ash) assert_equal "http://zombitar.com/#{z.id}.jpg", z.avatar_url end

/app/models/zombie.rb class Zombie < ActiveRecord::Base validates :name, presence: true def avatar_url "http://zombitar.com/#{id}.jpg" end end

MODEL TESTING

TestiNg a RelatioNship /test/unit/zombie_test.rb test "should respond to tweets" do z = zombies(:ash) assert_respond_to z, :tweets end test "should contain tweets" do z = zombies(:ash) assert_equal z.tweets, [?, ?] end

Need more fixtures MODEL TESTING

More Fixtures /test/fixtures/zombies.yml ash: id: 1 name: Ash graveyard: Oak Park

ash_1: status: Eating Eyebrows zombie_id: 1

tim: id: 2 name: Tim graveyard: Green Meadows

zombies(:ash).tweets

MODEL TESTING

/test/fixtures/tweets.yml

same

ash_2: status: Lurking 4 Brains zombie_id: 1

[tweets(:ash_2), tweets(:ash_1)]

TestiNg Zombie Tweets /test/unit/zombie_test.rb test "should contain tweets" do assert_equal [tweets(:ash_2), tweets(:ash_1)], zombies(:ash).tweets end

Brittle! Dependent on fixtures test "should contain only tweets that belong to zombie" do z = zombies(:ash) assert z.tweets.all? {|t| t.zombie == z } end

MODEL TESTING

cleaNiNg it up - Level 3 -

setup method /test/unit/zombie_test.rb test "should contain only tweets that belong to zombie" do z = zombies(:ash) assert z.tweets.all? {|t| t.zombie == z } end test "can generate avatar_url" do z = zombies(:ash) assert_equal "http://zombitar.com/#{z.id}.jpg", z.avatar_url end

Duplication! CLEANING IT UP

setup method /test/unit/zombie_test.rb def setup @z = zombies(:ash) end

Run before every test

test "should contain only tweets that belong to zombie" do assert @z.tweets.all? {|t| t.zombie == @z } end test "can generate avatar_url" do assert_equal "http://zombitar.com/#{ @ z.id}.jpg", @z.avatar_url end

CLEANING IT UP

RefactoriNg our Tests /test/unit/zombie_test.rb test "invalid name gives error message" do @z.name = nil @z.valid? assert_match /can't be blank/, @z.errors[:name].join, "Presence error not found on zombie" end test "invalid graveyard gives error message" do @z.graveyard = nil @z.valid? assert_match /can't be blank/, @z.errors[:graveyard].join, "Presence error not found on zombie" end

Duplication!

CLEANING IT UP

RefactoriNg our Tests /test/unit/zombie_test.rb def assert_presence(model, field) model .valid? assert_match /can't be blank/, , model.errors[field].join, "Presence error for #{field} not found on #{model.class}" end test "invalid name gives error message" do @z.name = nil assert_presence(@z, :name) end test "invalid graveyard gives error message" do @z.graveyard = nil assert_presence(@z, :graveyard) end

CLEANING IT UP

RefactoriNg our Tests /test/test_helper.rb

! t i e s u n a c s t s e t l l a w o N

class ActiveSupport::TestCase ... def assert_presence(model, field) model .valid? assert_match /can't be blank/, , model.errors[field].join, "Presence error for #{field} not found on #{model.class}" end end

/test/unit/zombie_test.rb test "invalid name gives error message" do @z.name = nil assert_presence(@z, :name) end test "invalid graveyard gives error message" do @z.graveyard = nil assert_presence(@z, :graveyard) end

INtroduciNg Shoulda Makes tests easy on the fingers and the eyes Gemfile group :test do gem 'shoulda' end

test "invalid name gives error message" do @z.name = nil assert_presence(@z, :name) end

using shoulda

should validate_presence_of(:name)

CLEANING IT UP

created by

UsiNg Shoulda

/test/unit/zombie_test.rb

class ZombieTest < ActiveSupport::TestCase should validate_presence_of(:name) should validate_presence_of(:graveyard) should ensure_length_of(:name).is_at_most(15) should have_many(:tweets) end

should validate_uniqueness_of(:name) should ensure_length_of(:password).is_at_least(5).is_at_most(20) should validate_numericality_of(:age) should_not allow_value("blah").for(:email) should allow_value("[email protected]").for(:email) should ensure_inclusion_of(:age).in_range(1..100) should_not allow_mass_assignment_of(:password) should belong_to(:zombie) should validate_acceptance_of(:terms_of_service)

Mocks & Stubs - Level 4 -

a bad Test /app/models/zombie.rb class Zombie < ActiveRecord::Base has_one :weapon

dependent on

def decapitate weapon.slice(self, :head) self.status = "dead again" end end

/test/unit/zombie_test.rb test "decapitate should set status to dead again" do @zombie.decapitate assert_equal "dead again", @zombie.status end

MOCKS AND STUBS

slice

We Need a Stub zombie decapitate

weapon def slice(args*) # complex stuff end

W e n e e d to fak /app/models/zombie.rb

class Zombie < ActiveRecord::Base has_one :weapon def decapitate weapon.slice(self, :head) self.status = "dead again" end end

MOCKS AND STUBS

e this call

INtroduciNg Mocha A library for mocking and stubbing Gemfile group :test do gem 'mocha' end

Stub For replacing a method with code that returns a specified result.

Mock A stub with an assertion that the method gets called.

MOCKS AND STUBS

We Need a Stub zombie

weapon

decapitate

def slice(*args) return nil end

/app/models/zombie.rb class Zombie < ActiveRecord::Base has_one :weapon def decapitate weapon.slice(self, :head) self.status = "dead again" end end

MOCKS AND STUBS

@zombie.weapon.stubs(:slice)

We Need a Stub zombie

weapon

decapitate

def slice(*args) return nil end

/test/unit/zombie_test.rb test "decapitate should set status to dead again" do @zombie.weapon.stubs(:slice) @zombie.decapitate assert "dead again", @zombie.status end

We need to test that slice is called MOCKS AND STUBS

TestiNg that slice gets called zombie

weapon

decapitate

def slice(*args) return nil end

/app/models/zombie.rb

@zombie.weapon.expects(:slice)

class Zombie < ActiveRecord::Base has_one :weapon def decapitate weapon.slice(self, :head) self.status = "dead again" end end

MOCKS AND STUBS

+ has an assert

TestiNg that slice gets called zombie

weapon

decapitate

def slice(*args) return nil end

/test/unit/zombie_test.rb test "decapitate should call slice" do @zombie.weapon.expects(:slice) @zombie.decapitate end

MOCKS AND STUBS

ANother example /app/models/zombie.rb class Zombie < ActiveRecord::Base def geolocate Zoogle.graveyard_locator(self.graveyard) end end

/test/unit/zombie_test.rb test "geolocate calls the Zoogle graveyard locator" do Zoogle.expects(:graveyard_locator).with(@zombie.graveyard) @zombie.geolocate end

stubs the method + assertion with correct param MOCKS AND STUBS

AddEd Complexity /app/models/zombie.rb def geolocate loc = Zoogle.graveyard_locator(self.graveyard) "#{loc[:latitude]}, #{loc[:longitude]}" end

/test/unit/zombie_test.rb test "geolocate calls the Zoogle graveyard locator" do Zoogle.expects(:graveyard_locator).with(@zombie.graveyard) .returns({latitude: 2, longitude: 3}) @zombie.geolocate end

stubs the method + assertion with correct param + return value

MOCKS AND STUBS

Need to test the returNed StriNg /app/models/zombie.rb def geolocate loc = Zoogle.graveyard_locator(self.graveyard) "#{loc[:latitude]}, #{loc[:longitude]}" end

/test/unit/zombie_test.rb test "geolocate returns properly formatted lat, long" do Zoogle.stubs(:graveyard_locator).with(@zombie.graveyard) .returns({latitude: 2, longitude: 3}) assert_equal "2, 3", @zombie.geolocate end

MOCKS AND STUBS

TestiNg with Object Stubs /app/models/zombie.rb

n r u t e r d shoul

def geolocate_with_object loc = Zoogle.graveyard_locator(self.graveyard) "#{loc.latitude}, #{loc.longitude}" end

object def latitude return 2 end def longitude return 3 end

/test/unit/zombie_test.rb test "geolocate_with_object properly formatted lat, long" do loc = stub(latitude: 2, longitude: 3) Zoogle.stubs(:graveyard_locator).returns(loc) assert_equal "2, 3", @zombie.geolocate_with_object end

MOCKS AND STUBS

INtegratioN Tests - Level 5 -

INtegratioN TestiNg Tests the full application stack Tests from the outside “Black box” testing Tests don’t invoke a full web server INTEGRATION TESTS

Why Not View & CoNtroller Tests? Views shouldn’t have logic Controllers shouldn’t have logic Integration tests have you covered

INTEGRATION TESTS

Rails INtegratioN comMaNds Low-level requests: get zombies_path post zombies_path, zombie: {name: 'Ash'} put zombie_path(zombie), zombie: {name: 'Bill'} delete zombie_path(zombie)

follow_redirect!

Exception raised for non-redirect responses INTEGRATION TESTS

Rails INtegratioN AssertioNs :success :redirect :missing :error

assert_response :success assert_response 200

Or any HTTP status symbol used by Rack assert_response :accepted

HTTP 202

assert_response :moved_permanently assert_response :service_unavailable

INTEGRATION TESTS

HTTP 301 HTTP 503

# # # #

HTTP HTTP HTTP HTTP

200 3XX 404 5XX

Rails INtegratioN AssertioNs assert_response :success assert_redirected_to root_url

assert_tag "a", attributes: {href: root_url} assert_no_tag "div", attributes: {id: 'zombie'} assert_select "h1", "Twitter for Zombies"

INTEGRATION TESTS

GeNeratiNg Rails Tests $ rails generate integration_test zombies invoke create

test_unit test/integration/zombies_test.rb

/test/integration/zombies_test.rb require 'test_helper' class ZombiesTest < ActionDispatch::IntegrationTest fixtures :all # test "the truth" do # assert true # end end

INTEGRATION TESTS

No Tests

TestiNg zombie show Page Twitter for Zombies

Ash



INTEGRATION TESTS

TestiNg zombie show Page Twitter for Zombies

Ash



INTEGRATION TESTS

TestiNg zombie show Page /test/integration/zombies_test.rb require 'test_helper' class ZombiesTest < ActionDispatch::IntegrationTest test "Anyone can view zombie information" do zombie = zombies(:ash) get zombie_url(zombie) assert_response :success assert_select "h1", zombie.name end end

INTEGRATION TESTS

Need something better? Navigate multiple pages Exercise forms and buttons Act more like a web browser

INTEGRATION TESTS

Capybara Simulates user activity Jonas Nickl

visit login_path

look in


Redirects are automatically followed

INTEGRATION TESTS

Capybara Setup add to /Gemfile group :test do gem 'capybara' end

add to /test/test_helper.rb require 'capybara/rails' class ActionDispatch::IntegrationTest include Capybara::DSL def teardown Capybara.reset_sessions! Capybara.use_default_driver end end

INTEGRATION TESTS

CApybara NavigatioN

text or elemen t i d click_link 'Homepage' form label, ele click_button 'Save' ment name, or click_on 'Link or Button text' i fill_in 'First Name', with: 'John' choose 'A Radio Button' check 'A Checkbox' uncheck 'A Checkbox' attach_file 'Image', '/path/to/image.jpg' select 'Option', from: 'Select Box'

current_path current_url

INTEGRATION TESTS

d

Modified Capybara Test /test/integration/zombies_test.rb require 'test_helper' class ZombiesTest < ActionDispatch::IntegrationTest test "Anyone can view zombie information" do zombie = zombies(:ash) visit zombie_url(zombie) assert_equal zombie_path(zombie), current_path within("h1") do assert has_content?(zombie.name) end end end

INTEGRATION TESTS

Capybara Checks has_content? 'Ash'

has_no_content? 'Ash'

within '#zombie_1' do has_content? 'Ash' end

has_selector? '#zombie_1 h1'

has_no_selector? '...'

has_selector? '#zombie_1 h1', text: 'Ash' has_selector? '.zombie', count: 5, visible: true

INTEGRATION TESTS

Capybara Checks has_selector?

DOM elements

has_content?

Textual content

has_link?

Hyperlinks

has_field?

Form fields

has_css?

DOM elements by CSS (default)

has_xpath?

INTEGRATION TESTS

DOM elements by XPath

TestiNg the behavior test "Navigation is available to the zombie page" do zombie = zombies(:ash) tweet = tweets(:hello) visit root_url within("#tweet_#{tweet.id}") do click_link zombie.name assert_equal zombie_path(zombie), current_path end end

INTEGRATION TESTS

TestiNg the behavior test "should create a new zombie" do visit root_url click_link "Sign Up" fill_in "Name", with: 'Breins' fill_in "Graveyard", with: 'BRREEEIIINNNSSS' click_button "Sign Up" assert_equal zombie_tweets_path("Breins"), current_path end

Extract the sign up process into a test helper method def sign_up_as(name, graveyard)

INTEGRATION TESTS

CleaNing the Tests test "should show a welcome message to new signups" do visit root_url sign_up_as 'Breins', 'BRREEEIIINNNSSS' assert has_content?("Welcome Breins"), "Message not displayed" end

INTEGRATION TESTS

UsiNg Factories - Level 6 -

Fixtures are frighteNing Associations are hard ash: id: 1 name: 'Ash' graveyard: 'Oak Park'

ash_hello_world: zombie_id: 1 status: 'Hello World'

Exponentially grow with edge cases Repeat a lot

USING FACTORIES

Factory Girl Implements factory pattern

Define valid model attributes Easily modifiable at runtime Inherit and modify configurations

USING FACTORIES

created by

Factory Girl add to /Gemfile group :development, :test do gem 'factory_girl_rails' end

created by

Load in development to get generator hooks USING FACTORIES

Factory Girl $ rails generate model zombie name:string graveyard:string ... invoke create

factory_girl test/factories/zombies.rb

/test/factories/zombies.rb FactoryGirl.define do factory :zombie do name "MyString" graveyard "MyString" end end

USING FACTORIES

INto the factory /test/fixtures/zombies.yml ash: name: 'Ash' graveyard: 'Oak Park'

zombies(:ash)

USING FACTORIES

/test/factories/zombies.rb FactoryGirl.define do factory :zombie do name 'Ash' graveyard 'Oak Park' end end

Factory(:zombie)

INto the factory Factory(:zombie)

Action to take FactoryGirl.create(:zombie)

FactoryGirl.build(:zombie)

FactoryGirl.attributes_for(:zombie)

USING FACTORIES

FactoryGirl.create(:zombie)

Factory to use

Returns a new, saved Zombie Returns a new, unsaved Zombie Returns a Hash of attributes

INto the factory /test/fixtures/zombies.yml

/test/factories/zombies.rb

ash: name: 'Ash' graveyard: 'Oak Park'

FactoryGirl.define do factory :zombie do name 'Ash' graveyard 'Oak Park'

bill: name: 'Bill' graveyard: 'Oak Park' mike: name: 'Mike' graveyard: 'Sunnyvale'

USING FACTORIES

factory :zombie_bill do name 'Bill' end factory :zombie_mike do name 'Mike' graveyard 'Sunnyvale' end end end

INto the factory /app/models/zombie.rb class Zombie < ActiveRecord::Base validates :name, presence: true, uniqueness: true validates :graveyard, presence: true end

zombie_1 = Factory(:zombie) # zombie_2 = Factory(:zombie) ActiveRecord::RecordInvalid: Validation failed: Name has already been taken

USING FACTORIES

INto the factory zombie_2 = Factory(:zombie)

zombie_2 = Factory(:zombie_bill)

zombie_2 = Factory(:zombie, name: 'Ash1')

FactoryGirl.define do factory :zombie do sequence(:name) { |i| "Ash#{i}" } graveyard 'Oak Park' end end

USING FACTORIES

Use another factory Use a unique name Use a sequence

INto the factory FactoryGirl.define do factory :zombie do sequence(:name) { |i| "Ash#{i}" } graveyard 'Oak Park' end end

zombie_1 = #

USING FACTORIES

A Zombie Factory 1000.times { Factory(:zombie) }

# # # # # # #

USING FACTORIES

Zombies Need weapoNs /app/models/weapon.rb class Weapon < ActiveRecord::Base belongs_to :zombie validates :zombie, presence: true end

/test/factories/weapon.rb FactoryGirl.define do factory :weapon do name 'Broadsword' association :zombie end end

USING FACTORIES

/test/factories/zombie.rb FactoryGirl.define do factory :zombie do ... end end

Zombies Need weapoNs class Zombie < ActiveRecord::Base has_one :weapon end

Class can’t be inferred FactoryGirl.define do factory :armed_zombie, class: Zombie do sequence(:name) { |i| "ArmedAsh#{i}" } association :weapon graveyard 'Oak Park' end end

USING FACTORIES

Zombies Need weapoNs FactoryGirl.define do factory :zombie do sequence(:name) { |i| "Ash#{i}" } graveyard 'Oak Park' factory :armed_zombie do association :weapon end end end

Inherits inferred class from parent zombie USING FACTORIES

Zombies Need weapoNs FactoryGirl.define do factory :zombie do sequence(:name) { |i| "Ash#{i}" } graveyard 'Oak Park' factory :armed_zombie do association :weapon, factory: :hatchet end end end

Override the inferred factory name USING FACTORIES

UsiNg FActories test "decapitate should set status to dead again" do zombie = zombies(:ash) zombie.decapitate assert_equal "dead again", zombie.status end

USING FACTORIES

UsiNg FActories test "decapitate should set status to dead again" do zombie = zombies(:not_dead_again_zombie) zombie.decapitate assert_equal "dead again", zombie.status end

USING FACTORIES

UsiNg FActories test "decapitate should set status to dead again" do zombie = FactoryGirl.build(:zombie) zombie.decapitate assert_equal "dead again", zombie.status end

USING FACTORIES

UsiNg FActories test "decapitate should set status to dead again" do zombie = FactoryGirl.build(:zombie, status: 'dead') zombie.decapitate assert_equal "dead again", zombie.status end

USING FACTORIES

UsiNg FActories test "Navigation is available to the zombie page" do zombie = zombies(:ash) tweet = tweets(:hello) visit root_url within("#tweet_#{tweet.id}") do click_link zombie.name assert_equal zombie_path(zombie), current_path end end

USING FACTORIES

UsiNg FActories test "Navigation is available to the zombie page" do zombie = Factory(:zombie) tweet = Factory(:tweet, zombie: zombie) visit root_url within("#tweet_#{tweet.id}") do click_link zombie.name assert_equal zombie_path(zombie), current_path end end

USING FACTORIES

UsiNg FActories test "Navigation is available to the zombie page" do tweet = Factory(:tweet) zombie = tweet.zombie visit root_url within("#tweet_#{tweet.id}") do click_link zombie.name assert_equal zombie_path(zombie), current_path end end

USING FACTORIES