//
you're reading...
Cucumber, Single table inheritance

Cucumber and Factory Girl testing when having Single Table Inheritance

To get familiar with Single Table Inheritance, please, refer to this article

Let’s assume, we have a web site requiring authentication and we have a User class that allows every user to log in, created with the gems Clearance and Suspenders such as described here, with a user name field added.

Now, let’s have 2 types of users with different functionality besides logging in: Company and Customer. Then we can have 2 classes inheriting from User.



class User < ActiveRecord::Base   
  include Clearance::User
end  

class Customer < User 
end  

class Company < User 
end 

In order to make the tables inherit, let’s add a “type” column to the “users” table.

$ rails generate migration add_type_to_users type:string

The 2 types of users will be processed by the same controller UsersController and will be routed with the same paths.

users_controller_spec.rb
-----------------------------
require 'spec_helper'

describe UsersController, 'routes' do
it { should route(:get, 'users/1').to(:action => 'show', :id => 1) }
end

and the corresponding mapping in routes.rb:

resources :users, :controller => 'users', :only => [:show, :create]

To test the models, we need to create separate factories for different user types. We will use FactoryGirl and FactoryGirl inheritance.

spec/factories/user.rb
--------------------------
Factory.sequence :email do |n|
 "user#{n}@example.com"
end

Factory.define :user do |user|
 user.email            { Factory.next :email }
 user.name             { "user" }
 user.password         { "password" }
 user.password_confirmation { |instance| instance.password }
end

Factory.define :email_confirmed_user, :parent => :user do |user|
 user.after_build { warn "[DEPRECATION] The :email_confirmed_user factory is deprecated, please use the :user factory instead." }
end

#add those 2 definitions

Factory.define :company, :class => Company, :parent => :email_confirmed_user do |user|
   user.type   { "Company" }
end

Factory.define :customer, :class => Customer, :parent => :email_confirmed_user do |user|
   user.type             { "Customer" }
end

After that, we would like to write some Cucumber features to test a user creating a profile of a certain type. The features were already created by Clearance, we only need to put checks for the extra attributes “name” and “user type”.

Feature: Sign up

 In order to get access to protected sections of the site
 As a visitor
 I want to sign up

 Background:
   When I go to the sign up page
   Then I should see an email field

 Scenario: Visitor signs up with invalid data
   When I fill in "Email" with "invalidemail"
   And I select "company" from "User type"
   And I fill in "User name" with "user"
   And I fill in "Password" with "password"
   And I fill in "Confirm password" with ""
   And I press "Sign up"
   Then I should see error messages


 Scenario: Visitor signs up with valid data
   When I fill in "Email" with "email@person.com"
   And I select "company" from "User type"
   And I fill in "User name" with "user"
   And I fill in "Password" with "password"
   And I fill in "Confirm password" with "password"
   And I press "Sign up"
   Then I should be on the "company" profile page for "user"
   And I should see "signed up"

 Scenario: Visitor tries to sign up without a user name
   When I go to the sign up page
   And I fill in "Email" with "email@person.com"
   And I select "company" from "User type"
   And I fill in "Password" with "password"
   And I fill in "Confirm password" with "password"
   And I press "Sign up"
   Then the "User name" field should have the "can't be blank" error

We make the first 2 scenarios pass by adding “type” and “user name” fields to the form, adding the “user name” to the model and the validation, creating a “show” view for “user”, and defining the missing path. I leave all of it as an exercise. We will also need to override the create method for Clearance, since it doesn’t handle User subtypes.

In UsersController add the following (it overrides the Clearance method)

 #overriden from Clearance to take care of inheritance
 def create
  @user = params[:user][:type].constantize.new params[:user]
  #used to be
  #@user = ::User.new params[:user]
   if @user.save
     flash_notice_after_create
     sign_in(@user)
     redirect_to(url_after_create)
   else
     render :template => 'users/new'
   end
 end

However, the third scenario still will have trouble passing since for some reason Rails refers to the inherited model when returning to the same page, instead of User. To fix this, we need to override the model_name method to User, so it returns the superclass model name.

class User < ActiveRecord::Base
 include Clearance::User

 validates_presence_of :name

 def self.inherited(child)
   child.instance_eval do
     def model_name
       self.superclass.model_name
     end
   end
   super
 end
end

Now, all the Cucumber tests should pass.

About these ads

About RailsBlogger

I'm a Software Developer with over 10 years of experience, Java and Ruby on Rails.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: