describe vs context in rspec. Differences?
There is not much difference between describe
and context
. The difference lies in readability. I tend to use context
when I want to separate specs based on conditions. I use describe
to separate methods being tested or behavior being tested.
One main thing that changed in the latest RSpec is that "context" can no longer be used as a top-level method.
RSpec: describe, context, feature, scenario?
The context
is an alias for describe
, so they are functionally equivalent. You can use them interchangeably, the only difference is how your spec file reads. There is no difference in test output for example. The RSpec book says:
"We tend to use
describe()
for things andcontext()
for context".
Personally I like to use describe
, but I can see why people prefer context
.
feature
and scenario
are a part of Capybara, and not RSpec, and are meant to be used for acceptance tests. feature
is equivalent to describe
/ context
, and scenario
equivalent to it
/ example
.
If you're writing acceptance tests with Capybara, use the feature
/ scenario
syntax, if not use describe
/ it
syntax.
Rails + Rspec: Patterns and Styles for it/describe/context descriptions?
Basically:
describe
is for "something". "Something" can be a instance or class method, or an action in features specs. ".method_name" if it is a class method and "#method_name" if it is a instance method.
context
is for describe a special case of spec (context is an alias fordescribe
). Usually start with "when".it
is what does 'something'. Usually start with "should".
describe ".to_s"
context "when is a number"
it "convert the number in a string"
context "when is a string"
it "return the same object"
But this is not strictly. Here's a guidance:
- https://www.relishapp.com/rspec/rspec-core/docs/example-groups/basic-structure-describe-it
- http://blog.teamtreehouse.com/an-introduction-to-rspec
- http://eggsonbread.com/2010/03/28/my-rspec-best-practices-and-tips/
Contexts in rspec
The structure I use when defining rspec files (based on reading I've done on rspec) is that you use describes
to describe specific functions, and context
to talk about a specific context of state and/or path through the function.
Example class:
class MyClass
def self.my_class_method(bool)
if bool == true
return "Yes"
else
return "No"
end
end
def my_instance_method
today = Date.today
if today.month == 2 and today.day == 14
puts "Valentine's Day"
else
puts "Other"
end
end
end
As you can see, I've defined a class method and an instance method that do really silly and random functions. But the point is this: the class method will do something different based on the argument, and the instance method will do something different based on some outside factor: you need to test all these, and these are different contexts. But we will describe the functions in the rspec file.
Rspec file:
describe MyClass do
describe ".my_class_method" do
context "with a 'true' argument" do
it "returns 'Yes'." do
MyClass.my_class_method(true).should eq "Yes"
end
end
context "with a 'false' argument" do
it "returns 'No'." do
MyClass.my_class_method(false).should eq "No"
end
end
end
describe "#my_instance_method" do
context "on Feb 14" do
it "returns 'Valentine's Day'." do
Date.stub(:today) { Date.new(2012,2,14) }
MyClass.new.my_instance_method.should eq "Valentine's Day"
end
end
context "on a day that isn't Feb 14" do
it "returns 'Other'." do
Date.stub(:today) { Date.new(2012,2,15) }
MyClass.new.my_instance_method.should eq "Other"
end
end
end
end
So you can see the describe
is for saying what method you're describing, and matches up with the name of a method in your class. The context
is used to evaluate different conditions the method can be called in, or different states that affect the way the method works.
Hope this helps!
What's the difference between RSpec's subject and let? When should they be used or not?
Summary: RSpec's subject is a special variable that refers to the object being tested. Expectations can be set on it implicitly, which supports one-line examples. It is clear to the reader in some idiomatic cases, but is otherwise hard to understand and should be avoided. RSpec's let
variables are just lazily instantiated (memoized) variables. They aren't as hard to follow as the subject, but can still lead to tangled tests so should be used with discretion.
The subject
How it works
The subject is the object being tested. RSpec has an explicit idea of the subject. It may or may not be defined. If it is, RSpec can call methods on it without referring to it explicitly.
By default, if the first argument to an outermost example group (describe
or context
block) is a class, RSpec creates an instance of that class and assigns it to the subject. For example, the following passes:
class A
end
describe A do
it "is instantiated by RSpec" do
expect(subject).to be_an(A)
end
end
You can define the subject yourself with subject
:
describe "anonymous subject" do
subject { A.new }
it "has been instantiated" do
expect(subject).to be_an(A)
end
end
You can give the subject a name when you define it:
describe "named subject" do
subject(:a) { A.new }
it "has been instantiated" do
expect(a).to be_an(A)
end
end
Even if you name the subject, you can still refer to it anonymously:
describe "named subject" do
subject(:a) { A.new }
it "has been instantiated" do
expect(subject).to be_an(A)
end
end
You can define more than one named subject. The most recently defined named subject is the anonymous subject
.
However the subject is defined,
It's instantiated lazily. That is, the implicit instantiation of the described class or the execution of the block passed to
subject
doesn't happen untilsubject
or the named subject is referred to in an example. If you want your explict subject to be instantiated eagerly (before an example in its group runs), saysubject!
instead ofsubject
.Expectations can be set on it implicitly (without writing
subject
or the name of a named subject):describe A do
it { is_expected.to be_an(A) }
endThe subject exists to support this one-line syntax.
When to use it
An implicit subject
(inferred from the example group) is hard to understand because
- It's instantiated behind the scenes.
- Whether it's used implicitly (by calling
is_expected
without an explicit receiver) or explicitly (assubject
), it gives the reader no information about the role or nature of the object on which the expectation is being called. - The one-liner example syntax doesn't have an example description (the string argument to
it
in the normal example syntax), so the only information the reader has about the purpose of the example is the expectation itself.
Therefore, it's only helpful to use an implicit subject when the context is likely to be well understood by all readers and there is really no need for an example description. The canonical case is testing ActiveRecord validations with shoulda matchers:
describe Article do
it { is_expected.to validate_presence_of(:title) }
end
An explict anonymous subject
(defined with subject
without a name) is a little better, because the reader can see how it's instantiated, but
- it can still put the instantiation of the subject far from where it's used (e.g. at the top of an example group with many examples that use it), which is still hard to follow, and
- it has the other problems that the implicit subject does.
A named subject provides an intention-revealing name, but the only reason to use a named subject instead of a let
variable is if you want to use the anonymous subject some of the time, and we just explained why the anonymous subject is hard to understand.
So, legitimate uses of an explicit anonymous subject
or a named subject are very rare.
let
variables
How they work
let
variables are just like named subjects except for two differences:
- they're defined with
let
/let!
instead ofsubject
/subject!
- they do not set the anonymous
subject
or allow expectations to be called on it implicitly.
When to use them
It's completely legitimate to use let
to reduce duplication among examples. However, do so only when it doesn't sacrifice test clarity. The safest time to use let
is when the let
variable's purpose is completely clear from its name (so that the reader doesn't have to find the definition, which could be many lines away, to understand each example) and it is used in the same way in every example. If either of those things isn't true, consider defining the object in a plain old local variable or calling a factory method right in the example.
let!
is risky, because it's not lazy. If someone adds an example to the example group that contains the let!
, but the example doesn't need the let!
variable,
- that example will be hard to understand, because the reader will see the
let!
variable and wonder whether and how it affects the example - the example will be slower than it needs to be, because of the time taken to create the
let!
variablle
So use let!
, if at all, only in small, simple example groups where it's less likely that future example writers will fall into that trap.
The single-expectation-per-example fetish
There is a common overuse of subjects or let
variables that's worth discussing separately. Some people like to use them like this:
describe 'Calculator' do
describe '#calculate' do
subject { Calculator.calculate }
it { is_expected.to be >= 0 }
it { is_expected.to be <= 9 }
end
end
(This is a simple example of a method that returns a number for which we need two expectations, but this style can have many more examples/expectations if the method returns a more complicated value that needs many expectations and/or has many side effects that all need expectations.)
People do this because they've heard that one should have only one expectation per example (which is mixed up with the valid rule that one should only test one method call per example) or because they're in love with RSpec trickiness. Don't do it, whether with an anonymous or named subject or a let
variable! This style has several problems:
- The anonymous subject isn't the subject of the examples — the method is the subject. Writing the test this way screws up the language, making it harder to think about.
- As always with one-line examples, there isn't any room to explain the meaning of the expectations.
- The subject has to be constructed for each example, which is slow.
Instead, write a single example:
describe 'Calculator' do
describe '#calculate' do
it "returns a single-digit number" do
result = Calculator.calculate
expect(result).to be >= 0
expect(result).to be <= 9
end
end
end
Advantage to using describe/it over feature/scenario in specs? (besides syntactic sugar)
I think the question is a bit broad, but it is possible to answer with some advice and opinions based on my own experience.
- Is there any advantage to writing tests with describe/it over feature/scenario? (Besides syntactic sugar)
Not as far as I know. However, you may find some convenient test framework features are easier to implement in one scheme than another.
- By using Capybara's feature/scenario, does it slow down the test suite? (Compared to using RSpec's keywords)
Just using the keywords will not be a large factor in speed of processing. What kind of web driver and host simulation you are using will have a larger impact.
- What exactly are the tests I am writing (as explained in the code block)? Acceptance, unit, a combination?
I would call them acceptance tests. However, there is not always a clear dividing line, and you need to look at how the tests will be run, and how they will be used in your development process.
A mature development pipeline may have two or three separate test suites used for different purposes, and probably implemented using different test frameworks. You might want a set of very fast tests (usually unit tests) implemented to run as a quick automated test of new code commits for instance.
- Would writing tests like the above alone achieve higher coverage? (Our next goal is >80%)
The tests can exercise any user-accessible feature of the application, and any of your own code that is exercised can be considered covered. It is likely you can get higher than 80% C0 coverage (Ruby coverage tools don't usually provide deeper details such as C1), provided you do not have a lot of utility scripts or other code that is not user-accessible.
I suspect using a specific test framework's keywords will have minimal impact. However, using Capybara to acceptance test the application via the web interface is going to be much slower than running lower-level unit tests of individual modules.
Speed of tests can vary orders of magnitude. For tight unit tests around a fast module, I might expect to run 100 examples per second. On a web development project, I typically run 10-20 examples per second on unit tests, but maybe 1 example per second on acceptance tests (which is roughly the ballpark you are getting here). When using Capybara via a browser driver on a hosted copy of a site, I might expect to run one example in 10 seconds, so a suite with over 100 tests has to be run only for critical-path tests, such as versus release candidates.
What's the difference between include_examples and it_behaves_like?
You probably know how to use describe
, context
, it
and specify
to clearly communicate one aspect of your code. The nested context provided by it_behaves_like
can be used to improve this communication with the reader.
I will base my example on the example given in the RSpec documentation for shared examples:
shared_examples "a collection" do
context "initialized with 3 items" do
it "says it has three items" do
# ...
end
end
end
describe Array do
it_behaves_like "a collection"
include_examples "a collection"
end
If you run RSpec with --format documentation
you get the following output:
Array
behaves like a collection
initialized with 3 items
says it has three items
initialized with 3 items
says it has three items
So the difference is how the spec is read eg in case of a failure.
Which style you prefer is a question of aesthetics of how you like your specs to read. Furthermore you would suggest to always use the same style if you work in a team to improve consistency.
Also, are it_should_behave_like and it_behaves_like just synonyms?
Almost, the context is named differently. it should behave like ...
vs behaves like ...
. Again a question of aesthetics.
rspec shared examples vs shared context
shared_examples
are tests written in a way that you can run them in multiple settings; extracting common behavior between objects.
it_behaves_like "a correct object remover" do
...
end
shared_contexts
is any setup code that you can use to prepare a test case . This allows you to include test helper methods or prepare for the tests to run.
include_context "has many users to begin with"
Related Topics
How to See the Ruby Code in a Proc
Is It a Bad Idea to Reload Routes Dynamically in Rails
How to Get Generators Call Other Generators in Rails 3
Mail Gem - How to Clean Up the Body String
Ruby Operator Overloading Question
Running Phantomjs from a Ruby on Rails Application
Sequel Accessing Many_To_Many Join Table When Adding Association
Ruby Rest-Client File Upload as Multipart Form Data with Basic Authenticaion
Attempting to Install Libv8, "Failed to Build Gem Native Extension"
How to Set a Cookie with a (Ruby) Rack Middleware Component
Can't Launch Simple Sinatra App Using Rackup and Jruby (No Response from Web Server)
Equivalent of Iconv.Conv("Utf-8//Ignore",...) in Ruby 1.9.X
Dealing with Large CSV Files (20G) in Ruby
What Rails Plugins Are Good, Stable and *Really* Enhance Your Code
Ruby on Rails Webpacker Can't Find Images Under Asset_Pack_Path
Trouble Downgrading Ruby on Os X Mavericks
Ending a Rails 2 Url with an Ip Address Causes Routing Error