RSpec

The value of tests

One of the most valuable benefits of tests is that they give you confidence that your code works as you expect it to work. Tests give you the confidence to do long-term development because with tests in place, you know that your foundation code is dependable. Tests give you the confidence to refactor your code to make it cleaner and more efficient.

Tests also save you time because tests help prevent regressions from being introduced and released. Once a bug is found, you can write a test for it, you can fix the bug, and the bug can never make it to production again because the tests will catch it in the future.

Another advantage is that tests provide excellent implicit documentation because they show exactly how the code is designed to be used.

Writing tests

Let’s take a look at how tests are best structured. All tests should follow the same basic structure.

1. Set up environment for testing

Typically, methods perform some sort of operation upon data. So in order to test your methods, you’ll need to set up the data required by the method. This might be as simple as declaring a few variables, or as complex as creating a number of records in database.

Your tests should always create their own test data to execute against. That way, you can be confident that your tests aren’t dependent upon the state of a particular environment and will be repeatable even if they are executed in a different environment from which they were written.

If you find that many of your tests require very similar setup code, be sure to properly decompose the setup code so that you don’t repeat yourself.

2. Call the method being tested

Once you have set up the appropriate input data, you still need to execute your code. If you are testing a method, then you will call the method directly.

3. Verify that the results are correct

Verifying that your code works as you expect it to work is the most important part of testing. Tests that do not verify the results of the code aren’t true tests. They are commonly referred to as smoke tests, which aren’t nearly as effective or informative as true tests.

4. Clean up environment

Environment always should be cleaned after a test running. That way, you can be confident that your next tests are not dependent upon the state of previous tests executing.

TDD

Test Driven Development (TDD) is not about writing tests. TDD is more than that, it’s a methodology. The main idea of TDD is to write tests before code.

‘Red’ – write failing test

This means that you have to have a failing test first. You can’t write any production code before ‘red’. Why? Because you have to know this test could fail in some circumstances and you have to know which change makes it pass.

‘Green’ – make the test pass

Write code that is only needed to make the test pass. Now, try to run the test again. WOW, passed! Do you think this is a bad solution? Doesn’t work fine? Sure it works fine, because the test passed. There is YAGNI principal (YAGNI stands for You Ain’t Gonna Need It) which says ‘don’t write more than you need at this moment’. If you are sure you need more, write test for it and then implement this functionality.

‘Refactor’ – clean up your code

Look at your code. Do you like it? Do you want to eat it? Do you want to f... it? If your answer to any of these questions was ‘no’, you should do something about that. Refactoring is changing code without changing its functionality.

The colorful iteration

Whole ‘red, green, refactor’ thing is about iteration, little programming cycles and fast feedback. When we write failing test we say ‘hey, my app should do that!’ Then we make it come true as fast as we can. It’s like in this game where you have to pass the ball to the next player before it ‘burns’ you. When you make a test pass, then you can relax and do refactoring. Change implementation, introduce design pattern and extract class or whatever you want. You have confidence that your code works all the time and that you didn’t break anything. This is the smallest programing cycle; this is exactly what TDD is about.

Installation

Setup envirepment

Install rspec

Running specs and formating output

Running

All specs

Specs in specific folder

Specific spec

Specific line in spec

Formating

Colorization

Documentation format

First test

spec/burger_spec.rb

Old syntax

spec/burger_spec.rb

First implementation

spec/burger_spec.rb

Describe

We use the describe() method to define an example group.

The describe() method takes an arbitrary number of arguments and an optional block, and returns a subclass of RSpec::Core::ExampleGroup.

Nested groups

Context

The context() method is an alias for describe().

What’s it() All About?

The it() method takes a single String, an optional Hash and an optional block.

String with 'it' represents the detail that will be expressed in code within the block.

spec/array_spec.rb

Pending

spec/array_spec.rb

Before and after hooks

Use before and after hooks to execute arbitrary code before and/or after the body of an example is run

before(:each)

Run before each example

spec/array_spec.rb

before(:all)

Run one time only, before all of the examples in a group

spec/array_spec.rb

Hooks run in order

spec/callbacks_spec.rb

Define hooks in configuration

file spec/callbacks_spec.rb

Let

Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.

Use before

Use let

Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.

Subject

Use subject in the group scope to explicitly define the value that is returned by the subject method in the example scope.

spec/array_spec.rb

Subject

spec/subject_spec.rb

Sharing helper methods

Including helper to each test

Described class

If the first argument to the outermost example group is a class, the class is exposed to each example via the described_class() method.

Filters

spec/subject_spec.rb spec/spec_helper.rb spec/spec_helper.rb

Shared Examples

Shared examples let you describe behaviour of types or modules. When declared, a shared group's content is stored. It is only realized in the context of another example group, which provides any context the shared group needs to run.

Including helper to each test

Shared context

Use shared_context to define a block that will be evaluated in the context of example groups either explicitly, using include_context, or implicitly by matching metadata.

Expectations

For checking the expectations, expect(obj).to and expect(obj).not_to methods are used.

Checking equality and identity

Equality Identity

The eq methods are used to express values equivalence, and equal are used when you want the receiver and the argument to be the same object.

How Expectations work

Old Syntax

Spec::Expectations adds two methods to Object:

Both methods take an optional Expression Matcher.

When should receives an Expression Matcher, it calls matches?(self). If it returns true, the spec passes and execution continues. If it returns false, then the spec fails with the message returned by matcher.failure_message.

Similarly, when should_not receives a matcher, it calls matches?(self). If it returns false, the spec passes and execution continues. If it returns true, then the spec fails with the message returned by matcher.negative_failure_message.

New Syntax

Matchers

RSpec ships with a number of useful Expression Matchers. An Expression Matcher is any object that responds to the following methods:

Spec::Matchers

Eql matcher

Passes if given and expected are of equal value, but not necessarily the same object.

Equal matcher

Passes if given and expected are the same object (object identity).

Spec::Matchers

Floating point numbers

Passes if given == expected +/- delta

When dealing with floating points, it's convenient to use matcher be_close(), which takes two arguments: the floating point number you are expecting and the precision you require.

Spec::Matchers

Regular Expressions

For checking strings to match regular expressions, the match is used. This can be very useful when dealing with multiple-line expectations.

Spec::Matchers

Predicate Matchers

A Ruby predicate method is a method that ends with a “?” and returns a boolean value, like string.empty? or regexp.match? methods. For these cases Rspec allows us to describe expectations with be_something matcher. When using a be_something matcher, RSpec removes the “be_”, appends a “?” and calls the resulting method in the receiver.

How it works

Alternately, for a predicate method that begins with "has" like Hash#has_key?, RSpec allows you to use an alternate form since "be_has_key" makes no sense.

Spec::Matchers

Include

Passes if given includes expected. This works for collections and Strings. You can also pass in multiple args and it will only pass if all args are found in collection.

Exist

Passes if given.exist?

Spec::Matchers

Changes

Sometimens you expect some code (wrapped in a proc) to change the state of some object. There is a convenient way to check it with rspec:

by(), to(), from()

Spec::Matchers

Have

RSpec provides several matchers that make it easy to set expectations about the size of a collection. There are three basic forms:

These work on any collection-like object-the object just needs to respond to #size or #length (or both). When the matcher is called directly on a collection object, the #items call is pure syntactic sugar. You can use anything you want here. These are equivalent:

You can also use this matcher on a non-collection object that returns a collection from one of its methods. For example, Dir#entries returns an array, so you could set an expectation using the following:

expect(Dir.new("my/directory")).to have(7).entries

A receiver IS a collection

A receiver OWNS a collection

Spec::Matchers

Custom matchers

When you find that none of the stock Expectation Matchers provide a natural feeling expectation, you can very easily write your own using RSpec’s matcher DSL or writing one from scratch.

Imagine that you are writing a game in which players can be in various zones on a virtual board. To specify that bob should be in zone 4, you could say:

But you might find it more expressive to say:

You can create such a matcher like so:

Also you can override the failure messages and the generated description:

Spec::Matchers

Chaining custom matchers

You can also create matchers that obey a fluent interface using the chain method:

And now it can be used as follows:

Doubles

A test double is an object that stands in for another object in an example.

The argument is a name, used for failure reporting, so you should use the role that the double is playing in the example.

Doubles, as_null_object

Use the as_null_object method to ignore any messages that aren't explicitly set as stubs or message expectations.

Method stubs

A method stub is a method that we can program to return predefined responses during the execution of a code example.

Method stubs can return different values on different calls: this will return 5.5% when a message for interest_rate is received for the first time, but will return 3% for subsequent calls/messages.

The mock object can be created with stubbed methods at once.

Stub methods can return values depending on the arguments.

Method stubs

Besides returning a value, method stub can yield a block, raise an exception, or throw the message.

unstub (or unstub!)

Removes a stub. On a double, the object will no longer respond to message. On a real object, the original method (if it exists) is restored.

Stubbing constants

Support is provided for stubbing constants. Stubbed constant names must be fully qualified; the current module nesting is not considered.

Stub on any instance of a class

Use any_instance.stub on a class to tell any instance of that class to return a value (or values) in response to a given message. If no instance receives the message, nothing happens.

Messages can be stubbed on any class, including those in Ruby's core library.

Stub a chain of methods

The stub_chain method lets you to stub a chain of methods in one statement - and there is no need to stub each method in the dependency chain represented by a chain of messages to different objects.



Stubs and before(:all)

Use before(:each), not before(:all)

Stubs in before(:all) are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in before(:all) would work in the first example that happens to run in that group, but not for any others.

Message expectations

A message expectation is an expectation that the test double will receive a message some time before the example ends. If the message is received, the expectation is satisfied. If not, the example fails.

For a negative expectation

Message expectations

Expecting Arguments

Arguments that are passed to with are compared with actual arguments received using ==. In cases in which you want to specify things about the arguments rather than the arguments themselves, you can use any of the matchers that ship with rspec-expectations.

Message expectations

Receive Counts

The implicit expectation is that the message passed to should_receive will be called once. You can make the expected counts explicit using the following

Message expectations

Ordering

When specifying interactions with a test double, the order of the calls is rarely important. In fact, the ideal situation is to specify only a single call. But sometimes, we need to specify that messages are sent in a specific order.

This example will pass only if the count( ) and add( ) messages are sent with the correct arguments and in the same order.

Message expectations

Setting responses

Whether you are setting a message expectation or a method stub, you can tell the object precisely how to respond. The most generic way is to pass a block to stub or should_receive:

When the double receives the msg message, it evaluates the block and returns the result.

The same responses are available, as for stub method:

When working with a partial mock object, you may occasionally want to set a message expecation without interfering with how the object responds to the message.

So, let's create an application

Introducing Codebreaker

Codebreaker is a logic game in which a code-breaker tries to break a secret code created by a code-maker. The code-maker, which will be played by the application we’re going to write, creates a secret code of four numbers between 1 and 6.

The code-breaker then gets some number of chances to break the code. In each turn, the code-breaker makes a guess of four numbers. The code-maker then marks the guess with up to four + and - signs.

A + indicates an exact match: one of the numbers in the guess is the same as one of the numbers in the secret code and in the same position.

A - indicates a number match: one of the numbers in the guess is the same as one of the numbers in the secret code but in a different position.

Selecting Stories

A great way to get started gathering user stories is to do a high-level brain dump of the sorts of things we might like to do. Here are some titles to get started:

Creating codebreaker gem

codebreaker.gemspec

Describing Code with RSpec

Now we're going to describe the expected behavior of instances of the Game class.

spec/spec_helper.rb spec/codebreaker/game_spec.rb

Describing Code with RSpec

So let's add some code structure

lib/codebreaker.rb lib/codebreaker/game.rb

Red: Start with a Failing Code Example

In game_spec.rb, we want to specify that when we start the game, it sends the right messages to the output.

spec/codebreaker/game_spec.rb

Green: Get the Example to Pass

The failure message tells us that output never received puts. Here’s what we need to do to get this example to pass:

lib/codebreaker/game.rb

Next step

The following failing step is the next thing to work on: And I should see "Enter guess:".

spec/codebreaker/game_spec.rb

Trying to get green again

This time, the output didn’t receive puts('Enter guess:'). Resolve that as follows:

lib/codebreaker/game.rb

Fix things up

We’ve told the double in the first example to expect puts( ) with “Welcome to Codebreaker!” and we’ve satisfied that requirement, but we’ve only told it to expect “Welcome to Codebreaker!” It doesn’t know anything about “Enter guess:”

spec/codebreaker/game_spec.rb

Refactor

"Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure."

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.

spec/codebreaker/game_spec.rb

/

#