RSpec

  1. Workflow 1.1. Scope 1.2. Committing
  2. core 2.1. subject scope 2.2. shared context
  3. expectations 3.1. collection matchers
  4. mocks 4.1. instance doubles
  5. command line interface
  6. Guard

Workflow

RSpec is a domain specific language that works as a testing framework which allows you to expect particular outputs from your code before you ever actually run it. This workflow means that you can you can manually generate error messages that inform you about the expected functionality of your code before you ever write a single line or “real” code.

Testing code in this way allows you to:

  1. Ensure that your code works as intended before it is shipped.
  2. Test sections of your code in isolation.
  3. Communicate to other developers how your code should function.
  4. Refactor your code cleanly.

The basic workflow in RSpec is to follow a Red - Green - Refactor coding loop. These steps are:

  1. Write a test that will fail (because there isn’t any code to pass it yet).
  2. Write the code to pass the test.
  3. Refactor the passing code maintaining its passing status.

This workflow ensures that you build your code with the intended functionality and when you refactor it you don’t accidentally break it. If you make refactoring changes to your code after you have a passing test you can ensure that as long as your tests still pass after the refactor your code still works in the same way, despite its form or method being altered.

Scope

You do not need to test private methods in RSpec. When testing we only want to confirm that our interface facing behaviour of our program is working correctly. Are we getting the correct inputs and outputs. A private method would only influence the internal parts of a class so it can be safely ignored as long our tests that measure the external outputs of our class are working as intended.

Committing

You should commit when:

  1. Your tests pass after writing a new piece of code.
  2. Your tests pass after refactoring your code.

File loading

If you load files directly in the code that RSpec test, i.e. with static path names from a File class then you will likely run into No File Found errors because the RSpec test function will run from a different relative directory. You can consistently load files in your code and in RSpec tests using the File.dirname(__FILE__) utility. This will return a file path that is absolute from the file that is being tested.

project/
├── lib/
│	├── my_app.rb
│   └── my_resource.json
├── spec/
│   └── my_app_spec.rb
├── resources/
│   └── another_resource.json
└── README.md

For example, in the above file structure if we were running out tests from the project directory directly and we had code in my_app.rb file that loads resources from my_resource.json file we could use the __FILE__ attribute with the File class and the name of the file appended. However, if we wanted to load the another_resource.json file we would need to reference it relative to the my_app.rb file because this is where the __FILE__ path points.

path = File.dirname(__FILE__)
my_resource = File.open(path + '/my_resource.json')
another_resource = File.open(path + '/../resources/another_resource.json')

Spec Helper

RSpec’s spec_helper file contains many of the configuration options that RSpec uses to run tests. Any methods of classes and set up that needs to be done for tests should be placed in the spec_helper.rb file.

Scripts

If you require a code file from within the spec_helper file that is a script. i.e. It doesn’t contain any methods or classes just code placed directly into the body of the file. This code will be run automatically when RSpec starts. But only once. This is useful for extracting some automatic initial set up out of the spec_helper file. For example, our script file might contain the following code that sets up an environment variable:

# spec_code.rb
ENV['ENVIRONMENT'] = 'rspec'

Because this code is placed directly into the body of spec_code.rb when we require it in our spec_helper file it is automatically run and our ENV variable is set.

# spec_helper.rb
require './spec_code'

# other spec helper code here

Test Output

You can customise the formatting output that RSpec gives you by editing the .rspec file in the root directory of your project. Commands inside this file are given with a -- double hyphen flag.

To add a color coded documentation mode to tests, which will display passing test titles use the color and documentation flags.

# .rspec
--require spec_helper
--color --format documentation

Core

If you do not define a top level describe value for your tests then subject will default to a String that matches the top level name of your tests.

Let

The let assigning function goes out of scope when you move into a new describe block. If you have nested describe blocks for classes, functions etc. then let name definitions will only persist within the describe block they were defined.

describe MyClass do
  let(:my_variable) { 10 }

  it "returns the value of my variable" do
    puts my_variable
    # => 10
  end

  describe "#something else" do
    it "tries to return the value of my variable but errors" do
      puts my_variable
      # => No Name Error
    end
  end
end

The above example shows how my_variable goes out of scope when a new describe block is initialised.

Subject Scope

You should not use the subject keyword inside a before block. This is because subject is re initialised for each example. In the below example we initialize subject.message in before but when we test the code with expect it returns nil, because subject was reinitialised as an empty object when the example was triggered.

# FAILS
before do
  subject = described_class.new
  subject.message = "pops"
end
it "responds with pops to cocoa" do
  expect(subject.cocoa).to eq("pops")
end
# => Expected "pops" but got "nil"

This also means that you can’t use described_class subject’s with constructors that require arguments because when RSpec initializes the subject inside an example it will not submit the requisite arguments.

# FAILS
before { subject = described_class.new("pops") }
it "responds with pops to cocoa" do
  expect(subject.cocoa).to eq("pops")
end
# => subject expected 1 argument but received 0

You can use any sort of instance variables from within a before block as well any that refer to described_class.

# PASSES
before { @my_subject = desribed_class.new("pops") }
it "responds with pops to cocoa" do
  expect(@my_subject.cocoa).to eq("pops")
end

You cannot use regular variables from within a before block.

# FAILS
before { my_subject = desribed_class.new("pops") }
it "responds with pops to cocoa" do
  expect(my_subject.cocoa).to eq("pops")
end
# => unitialized variable "my_subject"

If you want to define the subject before each example without the use of a custom instance variable (HINT: you should want to do this) then you must use a subject { ... } block at the context level.

# Both example PASS because the subject block is run on each example
subject { described_class.new("pops")
it "returns a string to cocoa" do
  expect(subject.cocoa).to be_a_kind_of(String)
end
it "responds with pops to cocoa" do
  expect(my_subject.cocoa).to eq("pops")
end

After initializing subject using a subject { ... } block you can alter the contents of subject from within before. This actually an enforced feature as editing subject outside of an it literal can only be done from within a boock. See the example after this next one for that!

# set up the subject object with a block
subject { subject.described_class.new("pops") }
# make changes and do further set up before each example
before { subject.messsage = "cops" }
it "responds with cops to cocoa" do
  expect(subject.cocoa).to eq("cops")
end

You can also just define (and alter) subject locally to an it block:

it "returns with pops to cocoa" do
  subject = described_class("pops")
  expect(my_subject.cocoa).to eq("pops")
end

You can add a class description and a block description to a describe block by submitting multiple comma separated arguments to the describe.

describe MyClass, '#my_method' do
  # ... testing code
end

You can create single like it blocks using one-liner syntax combined with the is_expected keyword. When testing a method output with is_expected set the output of an object to the subject.

# object one liner
subject { [1, 2, 3] }
it { is_expected.to not be_empty }
# method output one liner
subject { "Hello".upcase }
it { is_expected.to eq("HELLO") }

Structure

Use describe blocks with a # or . appended to their beginning for each method that you are testing.

describe ".include?" do
describe "#include?" do

Nest context within descriptions to indicate different paths and states that a method might encounter and it blocks to describe the expected output in clear terms. Keep descriptions short! Better Specs suggests a description length capped at 40 characters.

describe ".include?" do
  context "It contains element 3" do
    it "returns true" do
      expect(subject.include?).to be true
    end
  end
  context "It does not contain 4" do
    it "return false" do
      expect(subject.include?).to be false
    end
  end

Keep all it assertions separated unless the performance speed loss from separating them due to complex set at dependencies would several impact the functioning of your test suite.

Often you should prefer let over before, this is because “let is lazy” and so only runs when the object is needed.

Use Mocks sparingly, while they will increase the speed of your tests they make tests less usable and reliable in general.

Avoid using the imperative form “should” when describing test outcomes. Instead use the third person prescriptive form “does”.

it "should not change" do # --> BAD
it "does not change" do # --> GOOD

Shared Context

A shared context allows you to define re-usable context code for your examples. You can define a shared context inside a shared_context block that is separate from your main describe code. Shared context blocks must be placed at the beginning of your RSpec test file. You can define instance variables, methods and let statements inside a shared context. Instance variables must initialized inside a shared context must be contained in a before block.

# defining a shared context
shared_context 'context name' do
  before { @some_var = 10 }
  let(:another_var) { 20 }
  def shared_method
    return "This is a shared method."
  end
end

You can invoke a shared context by placing it inside a context block with the include_context method and the name of the context. Objects and methods define in the shared context can then be called from your example testing code as if they had been defined inside that context’s scope.

# call a context
context "my shared context" do
  include_context 'context name'
  # testing a shared context instance variable
  it "should return 10 for some_var" do
    expect(@some_var).to eq(10)
  end
  # testing a shared context let variable
  it "should return 20 for another_var" do
	  expect(another_var).to eq(20)
  end
  # testing a shared context method
  it "should return 'This is a shared method'" do
    expect(shared_method).to eq("This is a shared method.")
  end
end

You cannot:

# INVALID object initialization:
shared_context "non functional context" do
  # subject will not be initialized with input
  # when called in an example
  before do
    subject = described_class.new(:input)
    subject = MyClass.new(:input)
    # non instance variables are not valid unless
    # defined using let
    my_var = MyClass.new(:input)
  end
  # initialization of a variable outside a before 
  # block will result in a Nil return type when called
  my_var = MyClass.new(:input)
  @my_var = MyClass.new(:input)
end

It is however valid to use the described_class syntax within a shared context - the class instantiated will be taken implicitly from the describe block which your include_context statement appears in. You can also initialize subject from a shared context using a let block.

# VALID object initialization
shared_context "my context" do
  before
    @class_instance = MyClass(:input)
    @described_instance = described_class.new(:input)
  end
  let(:class_instance) { MyClass(:input) }
  # initialize subject as instance of described class
  # with shared contextual settings
  let(:subject) { described_class.new(:input) }
end

It is possible to define variables outside of a before or let block within a shared context block providing you only intend to use those variables within the scope of the shared context code.

# variable defined and used within shared context scope
shared_context 'context name' do
  input_var = 10
  let(:my_object) { MyClass.new(input_var) }
end

Pending Tests

You can define pending tests in your spec files by using the pending keyword in place of an it for the thing you test should do. This prints messages telling you which tests are not yet implemented. Very useful for the planning stage and making sure you have an indication of what tests you might write in the future. pending tests are not blocks you cannot place code in them.

describe "pending tests" do
  pending "must write a test here"
end

Expectations

A basic way to test argument input values is by expecting the output of the correct input to not raise an error.

# testing that method accepts 2 integers as input
expect { subject.method(2, 10) }.to_not raise_error

You can create a test for the number of arguments that a method should accept by using the respond_to syntax.

it { is_expected.to respond_to(:dock).with(1).argument }

Include matchers

You can check the contents of a hash’s key-value pair by using the include matcher with any arbitrary and literal hash input as its argument.

my_hash = { "a" => 2, :b => 3 }
it "contains kv pair 'a':2 and kv pair b:3" do
  expect(my_hash).to include("a" => 2)
  expect(my_hash).to include(:b => 3)
  # testing for multiple key-value pairs
  expect(my_hash).to include("a" => 2, :b => 3)
end

Collection matchers:

Collection matchers have been removed from RSpec expectations. To do collection matching you should now use the rspec-collection_matchers gem to run collection matching expectations. To install collection matching gem use:

gem install rspec-collection_matchers

Collection matchers allow you to check size and structure of collections without referencing their collection attributes (such as .length) directly. You must require collection matchers to use the collection matching code.

require 'rspec/collection_matchers'
it "matches the number of items in a collection" do
  expect([1, 2, 3]).to have_exactly(3).items
end

Mocks

Doubles

Test doubles allow you to define object’s that “stand in” for a real object in your system and will give and receive data in a predefined patterns as if they were an actual object. To create a double use:

dbl = double()
# create a double with an optional name
dbl = double("Name")

Doubles are strict, only responding to the messages which you say they should respond to. If you try to pass a message to a double that is not expecting you will get an received unexpected message error. To add a messages and return types to a double submit a list of hashes as part of the doubles constructor:

dbl = double("Name", :method_1 => 3, :method_2 => "hello")
# calling a method on a double
dbl.method_1
=> 3
# using a double's method in an example block
it "passes" do
  expect(dbl.method_2).to eq("hello")
end

You can make a double non-strict by adding .as_null_object to the end of the double creation. For any method its receives that is undefined it will return itself. These non-strict can also be created with strict return types as well.

# returning itself for all method calls
dbl = double("Name").as_null_object
dbl.a_method => dbl
# returning 2 for a_method and itself for all other method calls
dbl = double("Name", :a_method => 2).as_null_object
dbl.a_method => 2
dbl.b_method => dbl

You can add additional methods to a double after creation using the allow(...) and receive(...) methods. Where allow describes the object to receive the method and receive describes the name of the method to be received as a symbol. You can convert this syntax into a method stub by adding a code block { }afterwards with the return type.

# create a double that accepts no messages
dbl = double("Name")
# make dbl respond to the a_method message
allow(dbl).to receive(:a_method)
# make dbl respond to the a_method message with a pre-defined return
allow(dbl).to receive(:a_method) { 3 }

dbl.a_method => 3

You can use expect with receive to check if a method is called on an object during an example block. Below, the first example fails because a_method is never called during the block. In the second instance a_method is called at least once on the object so it passes. expect is separate from allow it does not check the object can receive a method only that it did. The method must be called after the receive statement for this to work.

# failing expect / receieve
it "fails" do
  dbl = double("Name")
  expect(dbl).to receive(:a_method)
end
=> F
# passing expect / receive
it "passes" do
  dbl = double("Name")
  expect(dbl).to receive(:a_method)
  dbl.a_method
end
=> .

You can also combined expect with a pre-defined output block.

# expect a method to be received and specify an output
dbl = double("Name")
expect(dbl).to receive(:a_method) { 10 }

Doubles and Class

One slight unintuitive piece of functionality related to doubles is that they can be used to mock class behavior by combining giving them a response to the .new method that a class might receive with return type of another double that repesents the instance that that class returns.

class CakeMaker
  def initialize(cake_class = Cake)
    @cake_class = Cake
  end

  def make_cake
    @cake_class.new
  end
end

class Cake
  # code to make a delicious make
end

The CakeMaker class takes in a class when created and when the make_cake method is called it returns an instance of the class that was passed when the CakeMaker was initialized. You can mock this dependency with by injecting one double into another.

describe CakeMaker do
  it 'makes a cake' do
    cake = double("cake")
    cake_class = ("cake class", :new => cake)
    subject = described_class.new(cake_class)
    expect(subject.make_cake).to eq(cake)
  end
end

A double is defined to represent the instance of Cake that is created and another doubles is created to represent the Cake class as a whole. This cake_class double has a method added to it that returns the double representing an instance. How the tests for CakeMaker work without relying on the class that would be made at all.

Partial Doubles (Receive)

Partial test doubles allow you to temporarily overwrite or extend the functionality of a real object for the purposes of a test. It follows the same syntax pattern as doubles allow(MyClass).to receive(:my_method) { my_return_value } and references the class within your system. This can also be combined with any subject keyword and instance calls within your tests.

class MyClass
	def my_method
		return 5
	end
end

it "returns a redefined value" do
  allow(MyClass).to receive(:my_method) { 0 }
  # where subject is an instance of MyClass
  expect(subject.my_method).to eq(0) => true
end
# => .

This also works with expect syntax shown above, to temporarily overwrite the output value and launch an example test for the method being called on the object

You can use the with matcher appended to receive to check if a method receives a particular set of arguments.

it "passes by receiving a method with argument 1" do
  dbl = double
  expect(dbl).to receive(:a_method).with(1)
  dbl.a_method(1)
end
# => .
it "fails by receiving a method with arugme '1'" do
  dbl = double
  expect(dbl).to receive(:a_method).with(1)
  dbl.a_method("1")
end
# => F

with can be used with regular expressions to match a range of different potential input arguments. In the example below it is also placed in a before block so that it can be run before

describe "method argument matching" do
  let(:dbl) { double }
  before { expect(dbl).to receive(:a_method).with(/ab/)
  it "passes absolute" do
    dbl.a_method("absolute")
  end
  it "passes abdominals" do
    dbl.a_method("abdominals")
  end
  it "fails acting" do
    dbl.a_method("acting")
  end
end

The anything keyword can be used to match with to any potential argument input.

describe "matching with anything" do
  let(:dbl) { double }
  before { expect(dbl).to receive(:a_method).with(anything)
  it "passes nil" do
    dbl.a_method(nil)
  end
  it "passes string" do
    dbl.a_method("hello")
  end
  it "passes int" do
    dbl.a_method(10)
  end
end

These with queries can be combined arbitrarily for testing multiple arguments.

expect(dbl).to receive(:a_method).with(1, anything, /abs/)

Redefinition from a double stops outside of the block in which it was created. However if you want to add a double of partial double before every test you should enclose the allow(...).to receive(...) inside a before { } block.

This is useful if you want to mock up the response of an object without effecting its functionality. For example, say we have a method in our object random_number which just returns a random number that determines the state of our program in some way. When testing we want to check for all the states that this random number could create without just running our code over and over again to get all the possible states or changing the code in random number so that it outputs the numbers that we want for testing purposes. We can simply use partial test doubles to temporarily overwrite the output of random_number for the purposes of a test. You can see an implementation of this in the Dice Messenger kata of my RSpec Katas repo.

Its also possible to check for receiving methods after they have been called using the have_received(:method) at the end of an example block as a way to make code that matches the “given-when-then” syntax more effectively. Any double or double partial that can receive a method can have it checked with the have_received syntax.

# using a double
it "passes" do
	dbl = double("Name", :method => 5)
	dbl.method
	expect(dbl).to have_received(:method)
end

# using a double partial
it "passes" do
	allow(MyClass).to receive(:method)
	MyClass.method
	expect(MyClass).to have_received(:method)
end

A spy is another way of creating a double that can respond to any method and work with the have_received(:method) in code. The implication here, is that by defining what was expected to be received at the end of the code you don’t have to allow methods to run on a double.

# using a spy
it "passes" do
	dbl = spy("Name")
	dbl.method
	expect(dbl).to have_received(:method)
end

have_received()can be combined with other matchers to check a range of different outcomes on what is received:

# checks if the method was called 
# with arguments 1 and then 2 in that order
expect(my_object).to have_received(:method).with(1).ordered
expect(my_object).to have_received(:method).with(2).ordered
# checks if a method was called at least x number of times
expect(my_object).to have_received(:method).at_least(3).times

The scope of test doubles is limited to individual examples and will be destroyed and re-created after each test. Trying to create a mock object in a context level test will result in a rspec-mocks outisde of the per-test lifecycle is not supported error.

You can put your code for defining test doubles inside a before(:example) { # set up doubles } block to keep your spec tests dry.

Its possible to define temporary scopes for doubles and use the output values of these with instance variables in your tests. This is done using the RSpec::Mocks.with_temporary_scope { # set up RSpec block } namespace code. I imagine that the main advantage of this is to show where derived values in your tests are meant to come from within the structure of your code.

before(:context) do
    RSpec::Mocks.with_temporary_scope do
      dbl = double(:foo => 13)
      @result = dbl.foo
    end
  end

  it "passes" do
    expect(@result).to eq(13)
  end

Verifying doubles

Double verification allows you to ensure that your double’s methods and attributes match a real object.

Instance double

An instance_double represents an instance of a real class and can verify that it really does receive a specified method. Attempting to add a method to a verifying double that does not exist on the class that double is attempting to mock will result in an error. Apart from this functionality verifying doubles work in the same way as regular doubles.

class MyClass
  def my_method
    return "hello"
  end
end

describe MyClass, '#my_method' do
  # verifies that my_method exists
  it "creates a working verified double" do
    my_class = instance_double(MyClass, :my_method => "Goodbye")
    my_class.my_method
  # => "Goodbye"
  end
  it "creates a failing verified double" do
    # errors when you try to add a method that 
    # doesn't exist on the original class
    my_class = instance_double(MyClass, :another_method => "Hello")
    # => the MyClass class does not implement the instance method another_method
  end
end

An instance_double must be created inside an it example block.

The class that the instance_double verifies with can be submitted as the literal class name or as a string. They can be used interchangeably.

# both of these are valid constructors for a MyClass verifying double
my_class = instance_double(MyClass)
my_class = instance_double("MyClass")

instance_double can also be used with the allow(...) and receive(...) syntax.

my_class = instance_double("MyClass")
# working method stubbing with verification
allow(my_class).to receive(:my_method) { "Goodbye" }
# failing method stubbing with verification
allow(my_class).to receive(:another_method) { "Bye!" }
# => the MyClass class does not implement the instance method another_method

instance_double like normal doubles do not automatically allow methods from the class it is verifying. These methods must be added using allow even if they exist on the verified class.

it "passes because method is allowed" do
  my_class = instance_double(MyClass)
  allow(my_class).to receive(:my_method) { "Goodbye" }
  my_class.my_method
end
# Even though MyClass impements my_method
# it wasn't explicitly allowed on this double
# so it fails
it "fails because method was not allowed" do
  my_class = instace_double(MyClass)
  my_class.my_method
  # => received unexpected message
end

instance_double will also check the arity (number of input arguments) for a method on a verifying double.

class Checker
  def check(n)
    puts "checked #{n}"
  end
end

describe Checker do
  it "passes with correct number of arguments" do
    my_checker = instance_double(Checker)
    allow(my_checker).to receive(:check) { "checked!" }
    my_checker.check(5)
  end
  # => .
  it "fails with wrong number of arguments" do
    my_checker = instance_double(Checker)
    allow(my_checker).to receive(:check) { "checked!" }
    my_checker.check(5, 10)
  end
  # => ArgumentError
end

instance_double will also check for required keyword arguments when called.

class Bar
  # required argument is baz
  def foo(baz:)
    puts "output: #{baz}"
  end
end

describe Checker do
  it "passes with required arguments" do
    bar= instance_double(Bar)
    allow(bar).to receive(:foo) { "Barred!" }
    bar.foo(baz: 7)
  end
  # => .
  it "fails with no required argument" do
    bar= instance_double(Bar)
    allow(bar).to receive(:foo) { "Barred!" }
    bar.foo(7)
  end
  # => Missing required keyword arguments: baz
end

You can use instance_double the expect(...) and receive(...) matchers to confirm that doubles being called by other objects are being interacted with correctly. When doing this methods do not need to be allowed because they are just having their calls checked.

class Mint
  def minty!
    # ..
  end
end

describe Mint do
  it "passes with a real method" do
    mint = instance_double("Mint")
    expect(mint).to receive(:minty!)
    mint.minty!
  end
  # => .
  it "fails with an unknown method" do
    mint = instance_double("Mint")
    expect(mint).to receive(:basket!)
    mint.basket!
  end
  # => does not implement method
end

instance_double can be combined with expect(...), receive(...), have_received(..) and with methods to create matchers for methods called on verifying doubles. The code below passes a verifying double of the Notifier class to a new instance of Caller with an expect block the create method to be called on the double.

class Caller
  def initialize(notifier)
    @notifier = notifier
  end

  def notify!
    puts @notifier.create("re-call", 12)
  end

  def reiterate!
    puts @notifier.profile("input", 20)
  end
end

class Notifier
  def create(name, times)
    # ...
  end
end

describe Caller do
  it "passes by calling create on notifier" do
    note = instance_double("Notifier")

    expect(note).to receive(:create).with("re-call", 12) { "re-called 12" }
    
    caller = Caller.new(note)
    caller.notify!
  end
  # => .
  it "fails by calling profile on notifier" do
    note = instance_double("Notifier")

    expect(note).to receive(:profile).with("input", 20) { "input 20" }
    
    caller = Caller.new(note)
    caller.reiterate!
  end
  # => Instance does not implement profile
end

Had an instance_double not been used in the above code the second example would have actually passed because (even though that situation would have had no relation to the actual functioning of our code) because a normal double would not check for any verification with the original object and would have only verified that the double received a message.

Private Instance Variables

Sometimes you will want to set the value of private instance variables of a class for the purpose of running tests. Consider the following classes.

class MyClass
  def initialize
    @my_object = MyObject.new
  end

  def get_report
    @my_object.report
  end
end

class MyObject
  def report
    return "Reporting!"
  end
end

MyClass contains a private instance variable @my_object which is an instance of MyObject. We do not want this variable to be publicly available to the rest of our program during normal functioning but we do want to interact with it during testing to influence the behaviour of MyClass. For example, what if we wanted to created a mock of @my_object inside MyClass? This wouldn’t be possible because that variable is private, but we still need to assign it as an RSpec mock to check some functionality. To do this we can use the instance_variable_set method. This allows to assign a private instance variable on an object as long as you know its names. The first argument specifies the name of the instance variable as a symbol and the second argument is the value that you want that variable to be assigned to.

describe MyClass do
  it 'has a mock object' do
    dbl = instance_double(MyObject, :report => "No Report!")
    subject.instance_variable_set(:@my_object, dbl)
    expect(subject.get_report).to eq('No report!')
  end
end

This works as intended with the subject’s private instance variable being replaced by our mock object that returns a different value when the report function is called.

Databases

You will often need to test objects or features that interface with database objects. Doing this with the live (production) version of your database would not be ideal as you could easily run into problems with data changing, or changing production data when testing. To solve this problem we can create a test database that is scrubbed clean of data before each test and only contains the data relevant to that test.

Setting up a testing database

To automate a testing database, we should:

  1. Set up a test database
  2. Create an environment variable and use it to tell our application whether we are using the test database or the live database (or some other database).
  3. Add a script to RSpec that scrubs database data before each test.

To set up a test database all we need to do is create a new database that has the same structure as our live database but has a different name. Generally we append the word _test to the end of the database name. So if our database was users or test database would be users_test.

We can then set a testing environment variable from within our spec_helper.rb file, because this runs when we first execute our tests.

# spec_helper.rb
ENV['ENVIRONMENT'] = 'test'

This can used as a conditional in classes that access the database.

if ENV['ENVIRONMENT'] == 'test'
  # connect to test database
else
  # connect to the live database
end

Next we create a ruby file that has code for scrubbing our database before each test contained in a method setup_test_database.

# setup_database.rb
def setup_test_database
  require 'pg'
  connection = PG.connect(dbname: 'bookmark_manager_test')
  connection.exec("TRUNCATE bookmarks;")
end

Then call this code using an configure block in the spec_helper.rb file with a before(:each) argument so that it runs before each test. This way the database will be TRUNCATEd before every test, removing data that might cause problems for our test.

# spec_helper.rb
require'./setup_database'
# runs before each test that is execute
RSpec.configure do |config|
  config.before(:each) do
    setup_test_database
  end
end

Finally within our tests themselves we can add any relevant data that needs to be tested before testing the class functionality that works with that data.

describe Connector do
  it 'gets database data' do
    connection = PG.connect(dbname: 'bookmark_manager_test')
    # Add the test data
    connection.exec("INSERT INTO test (name) VALUES ('Dec');")
    data = Connector.get_data
    expect(data).to include("Dec")
  end
end

CLI

You can run a specific RSpec tests by specifying a line number from the tests that falls within a block. For example if you had a test block which started on line 9, you could run only that test by using.

rspec ./spec/myclass_spec.rb:9

Guard

Guard is a framework for automatically running RSpec tests when files inside the directory change (i.e. are saved). You install guard by adding it to your Gemfile in the development group and then running the bundle command.

group :development, :test do
  gem "rspec"
  gem 'guard-rspec', require: false
end

Guard requires a Guardfile to run correctly. To set up the Guardfile use guard init rspec command. You can also use ` bundle exec guard init rspec`.

To run guard so that it automatically triggers testing when files change simply type the guard command. This will start guard running in the terminal window. Now when you save edited files in your project your RSpec tests will be automatically triggered. You can also use bundle exec guard. You can stop guard running by typing exit.

Written with StackEdit.