RSpec(Pt. 3): Test Doubles
Tags: rspec, ruby, testing, tdd
Published: 2020-08-15 17:12:01

Test Doubles in RSpec have been a little difficult to wrap my head around. No doubt you may have encountered many examples online of using test doubles, but you were not able to exaclty land down how or why you would use them in your tests.

[Resources](https://dev.to/ethanmgustafson/rspec-pt-3-test-doubles-3mml#resources)

# Why would you use them?

**Example 1**: You have one class written, but it depends on another class which is not yet written. You can create fake objects and methods of the missing class so that it could work with the class that is written. If the written class fails a test, then you know immediately the issue is with that class, since it is independent, not depending on other classes. This is where you can use _Mocks_ and _Stubs_.

- [Examples 2, 3 & 4](https://dev.to/ethanmgustafson/rspec-pt-3-test-doubles-3mml#partial-test-doubles)

# Mocks & Stubs

- A **Mock** is defined using the `double` keyword. It's essentially a fake object. It stands in for the object you want to test.
- A **Stub** is defined using the `allow` keyword. A Stub is a fake method you can use on the `double`. You use `allow` in order to define what messages are allowed to be called on the `double`.

If methods aren't given values, the stub returns a value of `nil` by default. You need to set what the return value will be of the stub.

```ruby
it "creates a mock and a stub" do
  dbl = double("greeting") 
  allow(dbl).to receive(:hello)
  expect(dbl.hello).to be_nil
end
```

Below, I stubbed methods when the double was created. To do that, you provide the double an argument of a hash. The hash keys will be set as the stubbing methods for the double.

```ruby
class Restaurant

    def initialize(workers)
      @workers = workers
    end
  
    def workers
      @workers.map{|w| w.name}
    end
  
end

require_relative '../restaurant.rb'
describe Restaurant do
    it "returns a list of workers" do
      dbl1 = double("manager", name: "Nancy") # The new fake objects from the missing class
      dbl2 = double("host", name: "Chris")
      dbl3 = double("waiter", name: "Michael")
  
      restaurant = Restaurant.new([dbl1, dbl2, dbl3])
  
      expect(restaurant.workers).to eq(["Nancy", "Chris", "Michael"])
    end
  end
```
Running the test:
```
➜  rspec spec

Restaurant
  returns a list of workers

Finished in 0.00341 seconds (files took 0.13641 seconds to load)
1 example, 0 failures
```

# Partial Test Doubles

A partial test double uses a real object from your application, but you stub its methods.

**Example 2**: You have a class, but certain methods are not yet written for that class.

```ruby
class Worker
end

describe Worker do
  it "greets customer" do
    dbl = double("worker")
    allow(dbl).to receive(:greet).and_return("Welcome!")
    expect(dbl.greet).to eq("Welcome")
  end
end
```

**Example 3**: You need to make a database call, but it would be faster to just use a double.

```ruby
describe Worker do
  it "stubs a database call" do
    dbl = double("worker", :name => "Ethan")
    allow(Worker).to receive(:find).and_return(dbl)
    worker = Worker.find
    expect(worker.name).to eq("Ethan")
  end
end
```

**Example 4**: When you have a object that is expensive to use, it would probably be better to test using mocks and stubs.

# Message Expectations

Ruby uses dot notation to send messages to objects. Those messages are methods. `"hello".capitalize` - `.capitalize` is the method, sending a message to the string `"hello"` which the string will receive and perform the message request. Then the value is returned as `"Hello"`

RSpec can also use expectations in this same way. The test will expect an object to receive a method, and then you call that method. 

```ruby
it "is a message expectation" do
  dbl = double("Cashier")
  expect(dbl).to receive(:take_order).and_return("Order when you're ready.")
  dbl.take_order
end
```

# Spies

Spies are exactly like doubles. The only difference is that you don't have to stub methods in order to make them available for use. Remember that in order to stub a method, you either provide a hash argument to a `double`, or you use the `allow` keyword to stub a method.

```ruby
it "host gives a greeting" do
  host_dbl = spy("Host")
  expect(host_dbl).to receive(:say_hello).and_return("Welcome!")
  host_dbl.say_hello
  expect(host_dbl).to have_received(:say_hello)
end
```
So you don't have to expect the `double` to receive the method and return a value, instead you can just see if the message has been received. 
```ruby
it "host gives a greeting" do
  host_dbl = spy("Host")
  host_dbl.say_hello
  expect(host_dbl).to have_received(:say_hello)
end
```

# Resources

More on Test Doubles: [Test Doubles](https://relishapp.com/rspec/rspec-mocks/docs/basics/test-doubles)

[RSpec Mocks](https://www.tutorialspoint.com/rspec/rspec_test_doubles.htm)

[RSpec Stubs](https://www.tutorialspoint.com/rspec/rspec_stubs.htm)

[RSpec Partial Test Doubles](https://relishapp.com/rspec/rspec-mocks/docs/basics/partial-test-doubles)

[RSpec Spies](https://relishapp.com/rspec/rspec-mocks/docs/basics/spies)

[Message Expectation Constraints](https://relishapp.com/rspec/rspec-mocks/docs/setting-constraints/matching-arguments)

[Message Count Constraints](https://relishapp.com/rspec/rspec-mocks/docs/setting-constraints/receive-counts)