Structured RSpec For Ruby & Rails
Tags: rspec, rails, ruby
Published: 2019-03-03 18:03:20

---
title: Structured RSpec For Ruby & Rails
published: true
description: A dive into my methods and thoughts for creating specs with ease for Ruby projects and some common Ruby On Rails patterns. 
tags: #rspec, #rails, #ruby
cover_image: https://thepracticaldev.s3.amazonaws.com/i/qim5k1vlhy21efgm654v.png
---


### Introduction

In my earlier years of software development, I paid little attention to the practice of building tests for my production applications. Of course my blood pressure, stress, and anxiety levels reached new peaks during the deployment of new code. And my reasons for not testing are I believe common amongst the community. It is thought that "writing tests take too long". I have realized that this feeling could be caused by the absence of structure and an underlying method that drives test/spec creation.

In this article, I share my thought process and methods (/w examples) for creating specs/test for my Ruby & Ruby On Rails projects. My methods are highly influenced by my previous mentors and [Better Specs](http://www.betterspecs.org/). I hope that you (the reader) after reading this article will be empowered to add test/spec creation in their everyday workflow. 

Let's feel good when we deploy new code :)

### What are you going to `describe` and what is your `subject`?

Without using `subject` or `describe` it can be hard to know exactly what the author of the spec was intending. I utilize `describe` in specs to keep track of what part of my code/method I am testing and `subject` to define it. Here is an example of a spec that describes some `Animal` object and a instance method called `#speak`:

```ruby
describe AnimalSpec do
  describe '#speak' do
    # Any call to `subject` will execute & return the output of the code block.
    # We now know that the author intends to test the output of #speak on varying
    # instances of `Animal` 
    subject { Animal.new.speak }
    
    it "should return an animal noise" do
      # The code beneath then does something more like: 
      # expect(Animal.new.speak).to eq('an animal noise')
      expect(subject).to eq('an animal noise')
    end
  end
end
```

This keeps things tidy and more importantly *focused*. That is, we are testing here the output of varying instances of `Animal`. Although this test isn't that interesting yet. Let's utilize `let` in the next section to enable us to match varying `context` and ultimately test the various outputs of your `subject`

### Let's use `let` to setup the `context`

In order to expand our specs to cover varying instances of `Animal` and their corresponding output for `#speak`, we will utilize `let` and `context`. The `let` method allows us to modify the output value of methods. And the `context` is made to describe in written words what situation the `subject` is in. Here is an example with the same `Animal` spec but this time add variance of the output by introducing the `animal_type` and `angry` parameter:

```ruby
describe AnimalSpec do
  describe '#speak' do
    subject { Animal.new(animal_type: animal_type, angry: angry).speak }
    
    context "when the animal_type is 'duck'" do
      # We utilize `let` to change the inputs of our subject to match
      # the situation we are describing in our context.
      let(:animal_type) { 'duck' }
      
      context "and the animal is not angry" do
        # Moreover, you can utilize `let` to continue to describe the
        # context further. 
        let(:angry) { false } 

        it "should return 'quack'" do
          expect(subject).to eq('quack')
        end
      end

      context "and the animal is angry" do
        let(:angry) { true }
        
        it "should return 'QUACK!'" do
          expect(subject).to eq('QUACK!')
        end
      end
    end
    
    context "when the animal_type is 'dog'" do
      let(:animal_type) { 'dog' }
      
      context "and the animal is not angry" do
        let(:angry) { false } 

        it "should return 'woof'" do
          expect(subject).to eq('woof')
        end
      end

      context "and the animal is angry" do
        let(:angry) { true }
        
        it "should return 'WOOF!'" do
          expect(subject).to eq('WOOF!')
        end
      end
    end
  end
end
```

By using `let` and `context` we can more clearly display our situations and varying characteristics of our `Animal` instances.

### Applying it to Ruby On Rails projects

Now that we've covered the basics for building specs for plain `ruby` code. Let's now dive a little deeper by putting this into practice for some common situations you may encounter when working with Rails.

#### Model Validation

Let's say you have a `User` database model that requires a valid unique email and a password. We can first create a `describe` block that says "validations" and a subject the returns the `#errors` after performing `#valid?`. In this approach, I define in the outermost context the valid attributes for a User and with `let` modify them into a invalid state which we can test generates an expected error:

```ruby

describe User do
  describe 'validations' do
    # I want to execute the `#valid?` operation to populate the errors and
    # then the errors for the following conditions. 
    subject { user.valid?; user.errors }
    
    let(:user) { User.new(user_attrs) }
  
    # Note - `let` (and `subject`) also accepts code blocks
    let(:user_attrs) do
      {
        email: email,
        password: password
      }
    end
    let(:email) { 'fake_valid_email@example.com' }
    let(:password) { 'fake_password' }

    context 'when the user_attributes are valid' do
      # No need to use `let` here as the outermost is already defining 
      # the valid attributes.
      it 'should have no errors' do
        expect(subject).to be_empty
      end        
    end
    
    context 'when the email is nil' do
      # This overwrites the value in the outer context.
      let(:email) { nil }

      # Be descriptive about what your are expecting to be returned
      # by your `subject`
      it "should have an error that says email can't be blank" do
        # `subject` is the errors hash as defined above. So we can 
        # dig in and see what errors are included.
        expect(subject[:email]).to include("can't be blank")
      end
    end

    context 'when the email is in an invalid format' do
      let(:email) { 'not_a_email' }

      it "should have an error that says email is invalid" do
        expect(subject[:email]).to include("is invalid")
      end
    end
    
    context 'when the email has been taken already' do
      # To trigger the expected uniqueness error we create User prior to calling
      # `subject` with the same email
      before do
        User.create!(email: email, password: password)
        # or also `User.create!(user_attrs)` works
      end

      it 'should have an error that says has already been taken' do
        expect(subject[:email]).to include('has already been taken')
      end
    end

    context 'when the password is nil' do
      let(:password) { nil }

      it "should have a error that says password can't be blank" do
        expect(subject[:password]).to include("can't be blank")
      end
    end
  end
end
```

This approach gives us a guideline to handling the various error states that a instance of `User` could be in. These ideas can be expanded to larger concepts or situations by merely changing the `subject` and the `let` that configure it into varying states.

#### Side-Effects Or Queueing Of Jobs

Once in a while you may encounter situations where you don't exactly care about the return value of your `subject` but rather the side-effects it has. For example, let's say you have a method on `User` called `#reset_password` in which you only care that the instance of `User` now has their password reset via a change event. Here we utilize the `-> { code }` (lambda) syntax to make it more apparent that we are interested in the effects of executing the code rather than the output:
 
```ruby
describe User do
  describe '#reset_password' do
    # The subject is now a lambda which you can trigger via `subject.call`
    # It will become more apparent why we want to do this in the next sections.
    subject { -> { user.reset_password } }

    let(:user) { User.create!(password: original_password) }
    let(:original_password) { 'original_password' }
    
    it "should change the password (method 1)" do
      # Check that the password is initially set to what you expect
      expect(user.password).to eq(original_password)
      subject.call
      # Use reload to ensure you get the latest copy of the `User` record
      expect(user.reload.password).not_to eq(original_password)
    end

    # Or asserting that the change happened via `change` matcher
    it "should change the password (method 2)" do
      expect { subject.call }.to change(user.reload.password)
    end
  end
end
```

Now let's say we wanted to add some extra functionality on `#reset_password` that enables us to specify that password reset instructions should be emailed to that user. Let's make that parameter be `send_instructions` and utilize mocking to check that the job or class responsible for sending the email has been called or not called. Let's call that job `PasswordResetSender`:

```ruby
describe User do
  describe '#reset_password' do
    subject { -> { user.reset_password(send_instructions: send_instructions } }

    # Let's mock out the class that would be triggering so we can see that it
    # was called were we expect it to be.
    before do
      allow(PasswordResetSender).to receive(:deliver)
    end

    context "when send_instructions is set to true" do
      let(:send_instructions) { true }
      
      it "should trigger a sending the password reset instructions" do
        subject.call
        # Check that the class responsible for sending the password instruction 
        # had been sent.
        expect(PasswordResetSender).to have_received(:deliver)
      end
    end

    context "when send_instructions is set to false" do
     let(:send_instructions) { false }

      it "should not trigger a sending the password reset instructions" do
        subject.call
        # Vice a versa, we want to ensure sending the password instruction does
        # not occur.
        expect(PasswordResetSender).not_to have_received(:deliver)
      end
    end
  end
end
```

## Conclusion

In this article, I have covered my thought process & methodology in applying RSpec to your Ruby or Ruby On Rails project. I hope this has been enlightening and inspiring to add test/spec into your regular workflow.

This is just one resource out of many that provides tips on how to apply RSpec effectively. One of my favorite sources where I drew inspiration for ideas is [BetterSpecs](http://www.betterspecs.org/).

Feel free to reach out to me if you have any questions or other situations that you want to know how to effectively spec out :).