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:
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
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:
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
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)
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:
To specify outcomes, you can use:
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;
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.
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.
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 }
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")
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.
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
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
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")
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).
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
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!
Kategorien
Schlagworte
Favoriteneinträge
Getting an e-Commerce website online might sound like a huge undertaking,...
WebView displays web pages. But we are interested not only in web-content...
Google Maps is a very famous and helpful service, which firmly entrenched...
RSpec is an integral part of Test Drive Development (TDD) and its main id...
When developing a web application that extensively works with user input ...
Field configuration defines behavior of all standart (system) fields and ...
As you might have already heard, the latest stuff for upgrading rails was...