15 Apr 2015
Unit Testing with RSpec

Unit Testing with RSpec

RSpec is an integral part of Test Drive Development (TDD) and its main idea is to help in simplifying and automating the testing process for functionality which is being developed. It's like a technical task for a developer or draft of  functionality the developer wants to get in result and testing tool to check the functionality for its conformity with its original goal at the same moment. So here bellow very simple basics regarding Rspec testing and I hope it will helps to understand the usability of this tool in project development and maintainance.

To test behavioral characteristics special model specs are used.  The model spec is a wrapper for an ActiveSupport::TestCase, and includes all of the behavior and assertions that it provides, in addition to RSpec's own behavior and expectations.

Examples

require "rails_helper"

RSpec.describe Post do
  context "with 2 or more comments" do
    it "orders them in reverse chronologically" do
      post = Post.create!
      comment1 = post.comments.create!(:body => "first comment")
      comment2 = post.comments.create!(:body => "second comment")
      expect(post.reload.comments).to eq([comment2, comment1])
    end
  end
end

Basically, model specs are used for: 

  • specifying validations, e.g.:
it "is valid with valid attributes" do
Message.new.should be_valid
end
it "is not valid without a title" do
message = Message.new :title => nil
message.should_not be_valid
end
  • specifying business rules;
  • focus on behavior: when thinking about models, it’s tempting to think of  all of the relationships and functionality we just know they’re going to need. Developing models this way can lead to inconsistent APIs with far too many public methods and relationships, which then become hard to maintain. That's why it is important to focus on clean and cohesive model;
  • while writing model specs also you may involve additional outcomes (for example, a user can send a message to a recipient), but the sender  has no way to review the messages he or she sent. Therefore, we need to add an expectation that the sender is associated with the message as well as the recipient. An example to express that expectation:
it "adds the message to the sender's sent messages" do 
zach = User.create! 
david = User.create! 
msg = zach.send_message(  :title => "Book Update",  :text => "Beta 11 includes great stuff!",  :recipient => david ) 
zach.sent_messages.should == [msg] 
end

Also it is useful to tidy up all stuff, which could be simplified: e.g. you could safely clean up the duplication between examples.

As to Associations – there is no reason to add them unless they are serving the needs of some behavior. For example, consider an Order that calculates its total value from the sum of the cost of its Items. We might introduce many items association to satisfy the relevant examples. 

Matchers

 rspec-rails provides some additional matchers that can be useful in model specs:

  • be_valid

The be_valid( ) matcher is used to set the expectation that your model is or is not valid

model.should be_valid 
model.should_not be_valid 
  •  error_on and errors_on

The error_on( ) and errors_on( ) methods extend RSpec’s have( ) matcher for use with ActiveRecord models in order to set an expectation that a particular attribute has an error or not. It will call valid?( ) on the model in order to prepare the errors.

model.should have(:no).errors_on(:title) 
model.should have(1).error_on(:body) 
model.should have(2).errors_on(:caption)
  • record and records

The record( ) and records( ) methods also extend the have( ) matcher for use with ActiveRecord models. These let us set an expectation of the number of records. It calls find(:all) on the model in order to determine the count.

ModelClass.should have(:no).records 
ModelClass.should have(1).recor

Controller specs

Controller specs live in spec/controllers or any example group with :type => :controller. A controller spec is an RSpec wrapper for a Rails functional test (ActionController::TestCase::Behavior). It allows you to simulate a single http request in each example, and then specify expected outcomes such as:

  • rendered templates,
  • redirects,
  • instance variables assigned in the controller to be shared with the view,
  • cookies sent back with the response.

To specify outcomes, you can use:

  • standard rspec matchers (expect(response.status).to eq(200))
  • standard test/unit assertions (assert_equal 200, response.status)
  • rails assertions (assert_response 200)
  • rails-specific matchers:
  • render_template   expect(response).to render_template(:new)   # wraps assert_template
  • redirect_to   expect(response).to redirect_to(location)   # wraps assert_redirected_to
  • have_http_status     expect(response).to have_http_status(:created)
  • be_a_new     expect(assigns(:widget)).to be_a_new(Widget)

Examples

RSpec.describe TeamsController do
  describe "GET index" do
    it "assigns @teams" do
      team = Team.create
      get :index
      expect(assigns(:teams)).to eq([team])
    end

    it "renders the index template" do
      get :index
      expect(response).to render_template("index")
    end
  end
end

Basic features:

controllers coordinate the interaction between the user and the application and should know what to do but not how to do it;

  • assigns():

We use assigns to access a hash, which we use to specify the instance variables that we expect to be assigned in the view. It is important to remember that the assigns hash in controller specs is different from the one in view specs. In view specs, we use assigns to set instance variables for a view before rendering the view. In controller specs, we use assigns to set expectations about instance variables assigned for the view after calling the controller action.

  • flash()

We use flash to access a hash, which we use to specify messages we expect to be stored in the flash. It uses the same API to access flash in the spec as you would use in the controller, which makes it convenient and easy to remember when working with flash.

  • post

We use the post( ) method to simulate a POST request. It can take three arguments. The first argument is the name of the action to call. The second argument (optional) is a hash of key / value pairs to make up the params. The third argument (also optional) is a hash of key / value pairs that make up the session hash for the controller.

# no params or session data
post :create
# with params
post :create, :id => 2
# with params and session data
post :create, { :id => 2 }, { :user_id => 99 }

 The post( ) method comes directly from ActionController::TestCase, which offers similar methods for get, put, delete, head, and even xml_http_request requests. All but the xml_http_request and its alias, xhr, have the same signature as the post( ) method. The xml_http_request( ) and xhr( ) methods introduce one additional argument to the front: the type of request to make. Then the other arguments are just shifted over. Here’s an example:

# no params or session data
xhr :get, :index
# with params
xhr :get, :show, :id => 2
# with params and session data
xhr :get, :show, { :id => 2 }, { :user_id => 99 }
  •  render_template

We use the render_template( ) method to specify the template we expect a controller action to render. It takes a single argument — the path to the template that we are rendering. The path argument can be in any of three forms. The first is the path to the template minus the app/views/ portion:

response.should render_template("messages/new")
# this will expand to "messages/new" in a MessagesController spec 
response.should render_template("new")
# controller action
defnew
respond_to :js, :html
end
# in the spec
get :new, :format => "js"
response.should render_template("new.js.erb") 
  • redirect_to

We use the redirect_to( ) method to specify that the action should redirect to a predefined location. It has the same API as its Rails’ counterpart, assert_redirected_to( ).

# relying on route helpers

response.should redirect_to(messages_path)

# relying on ActiveRecord conventions

response.should redirect_to(@message)

# being specific

response.should redirect_to(:controller => "messages", :action => "new")

Use bypass_rescue to bypass both Rails' default handling of errors in controller actions, and any custom handling declared with a rescue_from statement. This lets you specify details of the exception being raised, regardless of how it might be handled upstream.

For example:

classAccessDenied< StandardError; end
classApplicationController< ActionController::Base
  rescue_from AccessDenied, :with => :access_denied

  private

  defaccess_denied
    redirect_to "/401.html"
  end
end

So standard exception handling using `rescue_from`: 

require "rails_helper"
require 'controllers/gadgets_controller_spec_context'
RSpec.describe GadgetsController, :type => :controller do
  before do
    defcontroller.index
      raise AccessDenied
    end
  end

  describe "index" do
    it "redirects to the /401.html page" do
      get :index
      expect(response).to redirect_to("/401.html")
    end
  end
end

 and bypass `rescue_from` handling with `bypass_rescue` :

require "rails_helper"
require 'controllers/gadgets_controller_spec_context'
RSpec.describe GadgetsController, :type => :controller do
  before do
    defcontroller.index
      raise AccessDenied
    end
  end

  describe "index" do
    it "raises AccessDenied" do
      bypass_rescue
      expect { get :index }.to raise_error(AccessDenied)
    end
  end
end

View spec

View specs live in spec / views and render view templates in isolation. Basically, we provide data to the view and then set expectations about the rendered content. The main exception to this is forms; in that case we do want to specify that form elements are rendered correctly within a form tag. 

For example: 

require 'spec_helper' 
describe "messages/show.html.erb" do 
	it "displays the text attribute of the message" do 
		render 
		rendered.should contain("Hello world!") 
	end 
end

 

render(), rendered(), and contain()

Given no arguments, the render( ) method on the first line in the example renders the file passed to the outermost describe( ) block, “messages/show.html.erb” in this case. The rendered( ) method returns the rendered content, which is passed to the contain( ) matcher on the second line. If the rendered content contains the text “Hello world!” the example will pass. It is important to remember that this looks only at a rendered text. If “Hello world!” is embedded in a comment or in a JavaScript document.write statement, for example, it would not be recognized by contain( ).

assign()

View specs expose an assign method, which we use to provide data to the view. So it is possible to do like this:

describe "messages/show.html.erb" do 
	it "displays the text attribute of the message" do 
		assign(:message, double("Message", :text => "Hello world!")) 
		render 
		rendered.should contain("Hello world!") 
	end 
end 

 

Mocking Models

While writing specs there often rise a necessity of a model that doesn’t exist yet. Rather than switch focus to the model, we can create a mock_model( ) and remain focused on the view we’re working on. We can use the mock_model( ) method to provide a mock object that is configured to respond in this context as though it were an ActiveRecord model.

Mock Example

require 'spec_helper' 
describe "messages/new.html.erb" do 
   it "renders a form to create a message" do 
      assign(:message, mock_model("Message").as_new_record  ) 
      render 
      rendered.should have_selector("form",  :method => "post", :action => messages_path ) do |form| 
         form.should have_selector("input", :type => "submit") 
      end 
   end 
end 

 

for view template:

<%= form_for @message do |f| %>

<%= f.submit "Save" %>

<% end %> 

Difference between mock_model and stub_model

mock_model

The mock_model( ) method sets up an RSpec mock with common ActiveRecord methods stubbed out. In its most basic form, mock_model can be called with a single argument, which is the class you want to represent as an ActiveRecord model. The class must exist, but it doesn’t have to be a subclass of ActiveRecord::Base.

stub_model

The stub_model( ) method is similar to mock_model( ) except that it creates an actual instance of the model. This requires that the model has a corresponding table in the database.

Specifying Helpers

Rails helpers keep model transformations, markup generation, and other sorts of view logic cleanly separated from .erb templates. This makes templates clean and maintainable.

As an example of usage:

Consider the common problem of displaying parts of a view only to administrators. One nice solution is to use a block helper, like this:

<%- display_for(:admin) do -%>

Only admins should see this 

<%- end -%>

The rspec-rails plug-in provides a specialized ExampleGroup for specifying helpers in isolation. To see this in action, create a spec/helpers/ application_helper_spec.rb file. Assuming that views have access to a current_user( ) method, here’s an example for the case in which the current_user is in the given role:

require 'spec_helper' 
describe ApplicationHelper do 
	describe "#display_for(:role)" do 
		context "when the current user has the role" do 
			it "displays the content" do 
				user = stub('User', :in_role? => true) 
				helper.stub(:current_user).and_return(user) 
				content = helper.display_for(:existing_role) {"content"} 
				content.should == "content" 
			end 
		end 
	end 
end

The helper( ) method returns an object that includes the helper module passed to describe( ). In this case, that’s the ApplicationHelper.

moduleApplicationHelper
	defdisplay_for(role) 
		yield 
	end 
end 

Mailer specs

URL helpers in mailer examples

Mailer specs are marked by :type => :mailer or if you have set config.infer_spec_type_from_file_location! by placing them in spec/mailer. 

  • using URL helpers with default options

Given a file named "config/initializers/mailer_defaults.rb" with:

Rails.configuration.action_mailer.default_url_options = { :host => 'example.com' }

And a file named "spec/mailers/notifications_spec.rb" with:

require 'rails_helper'

RSpec.describe Notifications, :type => :mailer do
  it 'should have access to URL helpers' do
    expect { gadgets_url }.not_to raise_error
  end
end
  • using URL helpers without default options

Given a file named "config/initializers/mailer_defaults.rb" with: NO default options

And a file named "spec/mailers/notifications_spec.rb" with:

require 'rails_helper' 

RSpec.describe Notifications, :type => :mailer do
  it 'should have access to URL helpers' do
    expect { gadgets_url :host => 'example.com' }.not_to raise_error
    expect { gadgets_url }.to raise_error
  end
end

Routing specs

Routing specs are marked by :type => :routing or if you have set
config.infer_spec_type_from_file_location! by placing them in spec/routing.

Simple apps with nothing but standard RESTful routes won't get much value from routing specs, but they can provide significant value when used to specify customized routes, like vanity links, slugs, etc.

expect(:get => "/articles/2012/11/when-to-use-routing-specs").to route_to(
  :controller => "articles",
  :month => "2012-11",
  :slug => "when-to-use-routing-specs"
)

 They are also valuable for routes that should not be available:

expect(:delete => "/accounts/37").not_to be_routable
  • route_to matcher

The route_to matcher specifies that a request (verb + path) is routable. It is most valuable when specifying routes other than standard RESTful routes.

expect(get("/")).to route_to("welcome#index") # new in 2.6.0
or
expect(:get => "/").to route_to(:controller => "welcome")
  • be_routable matcher

The be_routable matcher is best used with should_not to specify that a given route should not be routable. It is available in routing specs (in
spec/routing) and controller specs (in spec/controllers).

  • named routes

Routing specs have access to named routes.

access named route:

Given a file named "spec/routing/widget_routes_spec.rb" with:

require "rails_helper"

RSpec.describe "routes to the widgets controller", :type => :routing do
  it "routes a named route" do
    expect(:get => new_widget_path).
      to route_to(:controller => "widgets", :action => "new")
  end
end
  • engine routes

Routing specs can specify the route setting that will be used for the example group. This is most useful when testing Rails engines.

specify engine route:

Given a file named "spec/routing/engine_routes_spec.rb" with:  

require "rails_helper"

# A very simple Rails engine
moduleMyEngine
  classEngine< ::Rails::Engine
    isolate_namespace MyEngine
  end

  Engine.routes.draw do
    resources :widgets, :only => [:index]
  end

  classWidgetsController< ::ActionController::Base
    defindex
    end
  end
end

RSpec.describe MyEngine::WidgetsController, :type => :routing do
  routes { MyEngine::Engine.routes }

  it "routes to the list of all widgets" do
    expect(:get => widgets_path).
      to route_to(:controller => "my_engine/widgets", :action => "index")
  end
end

Other code examples useful while writing rspec tests:

Hooks: Before, After, and Around

before(:each)

To group examples by initial state, or context, RSpec provides a before( ) method that can run either one time before :all the examples in an example group or once before :each of the examples. In general, it’s better to use before(:each) because that re-creates the context before each example and keeps state from leaking from example to example. Here is the simple example:

describe Stack do 
	context "when full" do 
	before(:each) do 
		@stack = Stack.new 
		(1..10).each { |n| @stack.push n } 
	end 
end 

The code in the block passed to before(:each) will be executed before each example is executed, putting the environment in the same known starting state before each example.

before(:all)

In addition to before(:each), we can also say before(:all). This gets run once and only once in its own instance of Object,1 but its instance variables get copied to each instance in which the examples are run. A word of caution in using this: in general, we want to have each example run in complete isolation from one another. As soon as we start sharing state across examples, unexpected things begin to happen. So, what is before(:all) actually good for? One example might be opening a network connection of some sort. Generally, this is something we wouldn’t be doing in the isolated examples that RSpec is really aimed at. If we’re using RSpec to drive higher-level examples, however, then this might be a good case for using before(:all).

after(:each)

Following the execution of each example, after(:each) is executed. This is rarely necessary because each example runs in its own scope, and the instance variables consequently go out of scope after each example. There are cases, however, when after(:each) can be quite useful. If you’re dealing with a system that maintains some global state that you want to modify just for one example, a common idiom for this is to set aside the global state in an instance variable in before(:each) and then restore it in after(:each), like this:

before(:each) do 
	@original_global_value = $some_global_value 
	$some_global_value = temporary_value 
end 

after(:each) do 
	$some_global_value = @original_global_value 
end

after(:each) is guaranteed to run after each example, even if there are

failures or errors in any before blocks or examples, so this is a safe

approach to restoring global state.

after(:all)

We can also define some code to be executed after(:all) of the examples in an example group. This is even more rare than after(:each), but there are cases in which it is justified. Examples include closing down browsers, closing database connections, closing sockets, and so on — basically, any resources that we want to ensure get shut down but not after every example.

around(:each)

RSpec provides an around( ) hook to support APIs that require a block. The most common use case for this is database transactions:

around do |example| 
	DB.transaction { example.run } 
end

RSpec passes the current running example to the block, which is then responsible for calling the example’s run( ) method. You can also pass the example to a method within the block as a block itself:

around do |example| 
	DB.transaction &example 
end

One trap of this structure is that the block is responsible for handling errors and cleaning up after itself. In the previous example, we assume that the transaction( ) method does this, but that is not always the case. Consider the following: 

around do |example| 
	do_some_stuff_before 
	example.run 
	do_some_stuff_after 
end 

So, if the example fails or raises an error, do_some_stuff_after( ) will not be executed, and the environment may not be correctly torn down. We could get around that with a begin/ensure/end structure, like this:

around do |example| 
	begin 
		do_some_stuff_before 
		example.run 
	ensure 
		do_some_stuff_after 
	end 
end 

But now this hook has a lot of responsibility, and the readability is getting weaker. For cases like this, it is recommended sticking to before and after hooks:

before { do_some_stuff_before }

after { do_some_stuff_after }

After hooks are guaranteed to run even if there is an error in an example or a before, so this removes the task of error handling we have in around hooks and is more readable.

Besides...

before(:each)

In this case, we have a very clear break between what is context and what is behavior, so let’s take advantage of that and move the context to a block that is executed before each of the examples. Modify game_spec.rb as follows:

require 'spec_helper' 
moduleCodebreaker
   describe Game do 
	describe "#start" do 
		before(:each) do 
			@output = double('output').as_null_object 
			@game = Game.new(@output) 
		end 
			
		it "sends a welcome message" do 
			@output.should_receive(:puts).with('Welcome to Codebreaker!') 
			@game.start 
		end 

		it "prompts for the first guess" do 
			@output.should_receive(:puts).with('Enter guess:') 
			@game.start 
		end 
	end 
   end 
end 

Just as you might expect from reading this, the block passed to before(:each) will be run before each example. The before block and the example are executed in the same object, so they have access to the same instance variables.

let(:method) {}

When the code in a before block is only creating instance variables and assigning them values, which is most of the time, we can use Rspec’s let( ) method instead. let( ) takes a symbol representing a method name and a block, which represents the implementation of that method. 

Here’s the same example as above, using let( ):

require 'spec_helper' 
moduleCodebreaker
   describe Game do 
	describe "#start" do 
		let(:output) { double('output').as_null_object } 
		let(:game)    { Game.new(output) } 
		it "sends a welcome message" do 
			output.should_receive(:puts).with('Welcome to Codebreaker!') 
			game.start 
		end 

		it "prompts for the first guess" do 
			output.should_receive(:puts).with('Enter guess:') 
			game.start 
		end 
	end 
   end 
end

The first call to let( ) defines a memorized output( ) method that returns a double object. Memorized means that the first time the method is invoked, the return value is cached and that same value is returned every subsequent time the method is invoked within the same scope.

Helper Methods

Another approach to cleaning up code from duplications is to use helper methods that could be defined right in the example group, which are then accessible from all the examples in that group. This approach is useful when there is a necessity to do the same action to several methods. 

describe Thing do 
	it "should do something when ok" do 
		thing = Thing.new 
		thing.set_status('ok') 
		thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil) 
		... 
	end 

	it "should do something else when not so good" do 
		thing = Thing.new 
		thing.set_status('not so good') 
		thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil) 
		... 
	end 
end 

 Both examples need to create a new Thing and assign it a status. This can be extracted out to a helper like this:

describe Thing do 
	defcreate_thing(options) 
		thing = Thing.new 
		thing.set_status(options[:status]) 
		thing 
	end 

	it "should do something when ok" do 
		thing = create_thing(:status => 'ok') 
		thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil) 
		... 
	end 

	it "should do something else when not so good" do 
		thing = create_thing(:status => 'not so good') 
		thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil) 
		... 
	end 
end 

Sharing Helper Methods 

If we have helper methods we want to share across example groups, we can define them in one or more modules and then include the modules in the example groups we want to have access to them.

moduleUserExampleHelpers
	defcreate_valid_user 
		User.new(:email => 'email@example.com', :password => 'shhhhh') 
	end 

	defcreate_invalid_user 
		User.new(:password => 'shhhhh') 
	end 
end  

describe User do 
	include UserExampleHelpers 
	
	it "does something when it is valid" do 
		user = create_valid_user 
		# do stuff 
	end 

	it "does something when it is not valid" do 
		user = create_invalid_user 
		# do stuff 
	end 
end

If we have a module of helper methods that we’d like available in all of our example groups, we can include the module in the configuration:

RSpec.configure do |config| 
	config.include(UserExampleHelpers) 
end 

Shared Examples

When we expect instances of more than one class to behave in the same way, we can use a shared example group to describe it once and then include that example group in other example groups. We declare a shared example group with the shared_examples_for( ) method.

shared_examples_for "any pizza" do 
	it "tastes really good" do 
		@pizza.should taste_really_good 
	end 

	it "is available by the slice" do 
		@pizza.should be_available_by_the_slice 
	end 
end

Once a shared example group is declared, we can include it in other example groups with theit_behaves_like( ) method. 

describe "New York style thin crust pizza" do 
	before(:each) do 
		@pizza = Pizza.new(:region => 'New York', :style => 'thin crust') 
	end 
	it_behaves_like "any pizza" 

	it "has a really great sauce" do 
		@pizza.should have_a_really_great_sauce 
	end 
end 

describe "Chicago style stuffed pizza" do 
	before(:each) do 
		@pizza = Pizza.new(:region => 'Chicago', :style => 'stuffed') 
	end 

	it_behaves_like "any pizza" 

	it "has a ton of cheese" do 
		@pizza.should have_a_ton_of_cheese 
	end 
end 

Nested Example Groups

Nesting example groups is a great way to organize examples within one spec. Here’s a simple example:

describe "outer" do 
	describe "inner" do 
	end 
end 

As we discussed earlier in this chapter, the outer group is a subclass of ExampleGroup. In this example, the inner group is a subclass of the outer group. This means that any helper methods and / or before and after declarations, included modules, and so on, declared in the outer group are available in the inner group. If we declare before and after blocks in both the inner and outer groups, they’ll be run as follows:

1. Outer before

2. Inner before

3. Example

4. Inner after

5. Outer after

Example: 

describe "outer" do 
	before(:each) { puts "first" } 
	describe "inner" do 
		before(:each) { puts "second" } 
		it { puts "third"} 
		after(:each) { puts "fourth" } 
	end 
	after(:each) { puts "fifth" } 
end 

What am I missing here? Let me know in the comments and I'll add it in!

Ähnliche Posts


Favoriteneinträge

What it Takes to Get an e-Commerce Site Online

Getting an e-Commerce website online might sound like a huge undertaking,...

WebView Interactions with JavaScript

WebView displays web pages. But we are interested not only in web-content...

Google Maps API for Android

Google Maps is a very famous and helpful service, which firmly entrenched...

Unit Testing with RSpec

RSpec is an integral part of Test Drive Development (TDD) and its main id...

Client side JavaScript: Knockout in practice

When developing a web application that extensively works with user input ...

Accessing Field Configurations in JIRA for changing field description

Field configuration defines behavior of all standart (system) fields and ...

A Guide for Upgrading to Ruby on Rails 4.2.0

As you might have already heard, the latest stuff for upgrading rails was...