# An introduction to RSpec: how to install RSpec, naming convention and basic files structure

> When you start programming, it's not easy to know what to test, how to test, and why should you test? So let's start with an introduction to RSpec: how to install RSpec, RSpec naming convention and and RSpec files structure.

Published on 2021-03-09 by Rémi Mercier.
Original: https://remimercier.com/rspec-101-basic-set-up/

When you start programming, it's not easy to know what to test, how to test, and why should you test? You've probably heard that TDD (as in Test Driven Development) is THE best practice. But at first, it's hard to know what your code should do before you're writing it.

Testing requires practice to reveal its potential.

When I started programming, I'd copy other people's tests because, well, I had to test, right? But after a while, my tests would uncover edge cases, potential bugs I'd overlooked.

So let's start with **an introduction to RSpec**: **how to install RSpec**, **RSpec naming convention** and and **RSpec files structure**.

This post will kick off a [series of RSpec fundamentals](/topics/rspec/) - short know-hows, know-whats and, know-whys - that could be of some use to newcomers. Brief essays explaining one aspect of testing with RSpec.

If this sounds fun to you, let's begin our introduction to the fantastic world of RSpec.

## What is RSpec?

First, let's begin with the obvious question: what is RSpec?

<mark>RSpec is a testing framework built in Ruby to test Ruby code. It focuses on testing the behavior of your program: what am I feeding my code? What do I expect to come out?</mark>

It's one of several testing frameworks available out there. You might also know Minitest.

## How to add RSpec to your application?

The RSpec team maintains a gem - `rspec-rails`-, making it easy to use the framework in Rails applications.

First, add RSpec to your Gemfile.

```ruby
  # Gemfile

  gem "rspec-rails", group: [:development, :test]
```

Install the gem.

```zsh
  bundle install
```

Scaffold RSpec's configuration and switch your application's testing framework to RSpec.

```zsh
  rails generate rspec:install
```

Now, run your migrations and prepare your test database.

```zsh
  rails db:migrate && rails db:test:prepare
```

There! Now, you can run your tests by typing `rspec spec` in your shell. `spec` is the folder where you'll create your test files.

## RSpec naming convention

RSpec naming convention is straightforward:

- `users_controller.rb` is tested by `users_controller_spec.rb`
- `user.rb` is tested by `user_spec.rb`
- `user_notification_job.rb` is tested by `user_notification_job_spec.rb`

## Architecture your spec folder

To make sure RSpec and Rails work smoothly together, <mark>mimick the structure of your `app` folder.</mark>

```zsh
  my_app_directory
  |
  |- app
  |  |
  |  |- models
  |     |
  |     |- user.rb
  |
  |- spec
     |
     |- models
        |
        |- user_spec.rb
```

- `app/models/user.rb` is tested by `spec/models/user_spec.rb`
- `app/serializers/admin/book_serializer.rb` is tested by `spec/serializers/admin/book_serializer_spec.rb`
- and so on.

There's only one catch:

- `app/controllers/users_controller.rb` is tested by `spec/requests/users_controller.rb`. The RSpec team discourages you to use `spec/controllers`. Why? Because testing requests allows you to test the behaviour of your controller's actions through the stack (routing, request, response, etc...) versus testing the controller in isolation [^1].

So for testing controllers, your folder's structure is:

```zsh
  my_app_directory
  |
  |- app
  |  |
  |  |- controllers
  |     |
  |     |- users_controller.rb
  |
  |- spec
     |
     |- controllers ❌
     |
     |- requests
        |
        |- users_controller_spec.rb
```

## The structure of your RSpec files

Let's say we want to test our `User` model. Our file's structure would look like this:

```ruby
  # spec/models/user_spec.rb

  require 'rails_helper'

  RSpec.describe User do
    # test stuff
  end
```

There! Your setup is done.

But now, I'd like us to dig into each element so we get a better understanding of what's going on.

- `require 'rails_helper'` loads the configuration for RSpec. `rails_helper.rb` is located at the root of the `spec` folder. RSpec is configured out of the box so no need to worry about it.

- `RSpec` is the core module for all RSpec code. It encapsulates and loads a lot of things on instantiation: configuration, expectations, examples, etc. You can check out the code [here](https://github.com/rspec/rspec-core/blob/main/lib/rspec/core.rb#L41){:target="\_blank"}.

- `.describe` is a class method defined on the `RSpec` module. It groups your tests around a common abstraction: a class, a request, etc. In the example above, our abstraction is the `User` model.

The code below is from [the RSpec repository](https://github.com/rspec/rspec-core/blob/fe3084758857f0714f05ada44a18f1dfe9bf7a7e/lib/rspec/core/dsl.rb#L42){:target="\_blank"}.

```ruby
  def self.expose_example_group_alias(name)
    return if example_group_aliases.include?(name)

    example_group_aliases << name

    (class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block|
      group = RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block)
      RSpec.world.record(group)
      group
    end

    expose_example_group_alias_globally(name) if exposed_globally?
  end
```

It's a bit hard to read because of the metaprogramming bits, but the main idea is that it defines the `.describe` instance method in the `RSpec::Core::ExampleGroup` class with the abstraction you're testing (`User`) and the tests you wrote as arguments.

- `User` is the class you're testing. It's passed as an argument to the `.describe` method.

- `do ... end` is the block where you're writing your tests. These will be passed as a second argument to the `.describe` method (see `&example_group_block` above).

I hope these explanations will give you a better understanding of how RSpec works. Next time, we'll write our first tests.

Noticed something? [Ping me on Twitter](https://twitter.com/mercier_remi) or [create an issue on GitHub](https://github.com/merciremi/remicodes/issues/new).

Cheers,

Rémi

[^1]: Thanks [@Benoit](https://twitter.com/Benoit_Tgt){:target="\_blank"} for pointing that out!
