Skip to content

Instantly share code, notes, and snippets.

@justinko
Created February 23, 2012 20:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinko/1894922 to your computer and use it in GitHub Desktop.
Save justinko/1894922 to your computer and use it in GitHub Desktop.
Help for rspec-users mailing list question
require 'spec_helper'
shared_examples_for 'employee signin' do
it { should have_selector('title', text: employee.emp_full_name) }
it { should have_link('Sign out', href: signout_path) }
it { should_not have_link('Sign in', href: signin_path) }
end
describe "Authentication" do
subject { page }
describe "signin" do
before { visit signin_path }
it { should have_selector('h2', text: 'Sign in') }
it { should have_selector('title', text: 'Sign in') }
describe "with an employee" do
let(:employee) { FactoryGirl.create(:employee) }
before { valid_signin(employee) }
include_examples 'employee signin'
it { should_not have_link('Employees', href: employees_path) }
it { should_not have_link('Profile', href: employee_path(employee)) }
it { should_not have_link('Settings', href: edit_employee_path(employee)) }
it { should_not have_link('New Employee', href: new_employee_path) }
end
describe "with an admin employee" do
let(:employee) { FactoryGirl.create(:admin_employee) }
before { valid_signin(employee) }
include_examples 'employee signin'
it { should have_link('Employees', href: employees_path) }
it { should have_link('Profile', href: employee_path(employee)) }
it { should have_link('Settings', href: edit_employee_path(employee)) }
it { should have_link('New Employee', href: new_employee_path) }
end
end
end
# If you *really* want to get DRY
require 'spec_helper'
shared_context 'employee signin' do |employee_type|
let(:employee) { FactoryGirl.create(employee_type) }
before { valid_signin(employee) }
it { should have_selector('title', text: employee.emp_full_name) }
it { should have_link('Sign out', href: signout_path) }
it { should_not have_link('Sign in', href: signin_path) }
case employee_type
when :employee
it { should_not have_link('Employees', href: employees_path) }
it { should_not have_link('Profile', href: employee_path(employee)) }
it { should_not have_link('Settings', href: edit_employee_path(employee)) }
it { should_not have_link('New Employee', href: new_employee_path) }
when :admin_employee
it { should have_link('Employees', href: employees_path) }
it { should have_link('Profile', href: employee_path(employee)) }
it { should have_link('Settings', href: edit_employee_path(employee)) }
it { should have_link('New Employee', href: new_employee_path) }
end
end
describe "Authentication" do
subject { page }
describe "signin" do
before { visit signin_path }
it { should have_selector('h2', text: 'Sign in') }
it { should have_selector('title', text: 'Sign in') }
describe "with an employee" do
include_context 'employee signin', :employee
end
describe "with an admin employee" do
include_context 'employee signin', :admin_employee
end
end
end
@jbrains
Copy link

jbrains commented Feb 25, 2012

I really dislike the second version, because it's like looping, but executing a different branch during each iteration of the loop.

If you really want to be DRY, then describe each set of expectations as data:

describe...
  it { should have_links( 'Employees' => { href: employees_path }, 'Profile' => { href: employee_path(employee) }, ...)
end

describe...
  it { should_not have_links( 'Employees' => { href: employees_path }, 'Profile' => { href: employee_path(employee) }, ...)
end

This requires a custom matcher, so perhaps this is easier:

describe...
  { 'Employees' => { href: employees_path }, 'Profile' => { href: employee_path(employee) }, ... }.each do | link_text, link_properties |
    it { should have_link(link_text, link_properties) }
  end
  {}.each do | link_text, link_properties |
    it { should_not have_link(link_text, link_properties) }
  end
end

describe...
  {}.each do | link_text, link_properties |
    it { should have_link(link_text, link_properties) }
  end
  { 'Employees' => { href: employees_path }, 'Profile' => { href: employee_path(employee) }, ... }.each do | link_text, link_properties |
    it { should_not have_link(link_text, link_properties) }
  end
end

I left the empty dictionaries in there just for clarity. You might remove them.

@justinko
Copy link
Author

I really dislike the second version

Same here. Didn't really think about what I was doing.

@jbrains
Copy link

jbrains commented Feb 25, 2012

I really dislike the second version

Same here. Didn't really think about what I was doing.

No problem. It helped get us here.

@zdennis
Copy link

zdennis commented Feb 26, 2012

Out of all of the ones listed I like @justinko's 1st version. It's the simplest, clearest, and easiest to understand and change without having to study the code. If you wanted your top-level "authentication" spec to be more declarative then I'd extract the different examples out into two shared contexts. This is similar to @justinko's 2nd version, but avoids the unnecessary case statement.

Here's a take on that:

require 'spec_helper'

shared_context "a user signs in" do
  it { should have_link('Sign out',      href: signout_path) }
  it { should_not have_link('Sign in',   href: signin_path) }
  it { should have_selector('title',     text: employee.emp_full_name) }
end

shared_context 'an employee signs in' do
  let(:employee) { FactoryGirl.create(:employee) }
  before { valid_signin(employee) }

  include_examples "a user signs in"

  it { should_not have_link('Employees',     href: employees_path) }
  it { should_not have_link('Profile',       href: employee_path(employee)) }
  it { should_not have_link('Settings',      href: edit_employee_path(employee)) }
  it { should_not have_link('New Employee',  href: new_employee_path) }
end

shared_context 'an admin employee signs in' do
  let(:employee) { FactoryGirl.create(:admin_employee) }
  before { valid_signin(employee) }

  include_examples "a user signs in"

  it { should have_link('Employees',     href: employees_path) }
  it { should have_link('Profile',       href: employee_path(employee)) }
  it { should have_link('Settings',      href: edit_employee_path(employee)) }
  it { should have_link('New Employee',  href: new_employee_path) }
end

And then in your authentication_spec something like:

describe "Authentication" do
  subject { page }

  describe "signin" do
    before { visit signin_path }

    it { should have_selector('h2',     text: 'Sign in') }
    it { should have_selector('title',  text: 'Sign in') }

    include_examples 'an employee signs in'
    include_examples 'an admin employee signs in'
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment