How did I improve RSpec? (Part 1)

Tram Ho


Rails is the framework for the Ruby language and is being used strongly.

RSpec is one of the most popular testing frameworks for Ruby. RSpec-rails is an extension of RSpec, which allows you to write unit tests for controllers, views, helpers and models in the Rails application. It also provides the ability to write integration tests with selenium (using capybara).

So why use RSpec? There are several reasons that I see from the actual development of the project are:

  • Write unit tests to check if the code you just wrote works.
  • Help the dev to list possible cases for the code I’ve just written. Thereby helping to detect cases when the code is missing, not thought out. It is also possible to reproduce cases where the manual test has not been tested.
  • This is an effective form of explanation, helping reviewers and teamates read and understand code faster.
  • When writing good unit tests, maintaining projects will minimize risks and save much effort.

So, how to write rspec short but still complete, easy to understand?

Improve how to describe each case

Because writing rspec to check the code written in each case to see whether it is correct or not. So the description of these cases is a relatively important part. Rspec uses describe, context and it to do this.

Describe and context helps divide unit tests into blocks, which can be considered as test cases. They help describe the case that is happening, which makes the test more readable and more comprehensive. Usually, the describe will include the context and describe smaller. The difference between describe and context is actually not much, it just thinks convention is essential. Describe should be used when describing methods or functions that are written to test. The context then describes the cases that happen to that method or function. For each case that will behave how to use it. A few notes about format:

Context should start with when / which:

It should start with the verb:

For example:

You should write rspec with type structure:

One more note: To avoid the complexity of the example description, instead of using if in it, put the content of that if on the context.

Improve speed

The speed of rspec heavily depends on creating pre-condition records. The record creation will mainly be via gem factorybot .

FactoryBot provides a skeleton for generating data through factory files. In order to understand better, in this section I will consider the example with model item and model category related: item belongs to category

  • FactoryBot.create: create the related instance and association, then return the instance after it has been saved in the database. For example, when FactoryBot.create (: item), in addition to item, a category will also be created

Besides, the call back related to the creation of the item object will also be called as before (: create), after (: create), after (: build).

  • build the related instance and association, however, it will not trigger the database but will return that instance as well. For example, when (: item) it will simply be

The callback here will only have after (: build).

  • FactoryBot.build_stubbed: create an object and assign attribute values ​​to that object, and like build , won’t trigger into the database.

It can be seen that the values ​​id, created_at, updated_at are all different from nil. However, when Item.find( will return an exception, because this id value is fake, does not exist. Callback is called after (: build).

So, in examples, instead of using create , take advantage of build and build_stubbed thoroughly, because the less you manipulate the database, the faster test speed will be.

For example, instead of an item that belongs to only one category, the item will belong to a list of categories, saved by the category_ids field with a string data type. And to get the same list item that belongs to a category, there is scope

To write tests for this scope, the input data I created is:

I used build_stubbed to create data instead of create. And there

DRY unit test

Of course, don’t repeat yourself (DRY) code then try DRY unit test too. And to do this, use share_examples and share_context

  • share_example :
    • is interpreted as an example, including pre-condition (may or may not), behavior
    • Used via: include_examples , it_behaves_like
  • share_context :
    • is understood as a context, only pre-condition
    • Used via: include_context

For example: Assuming there is a requirement: every user login the system on January 1, 2020, will be awarded 1000 points. Then, the unit test will be:

Use the travel_to helper

Here, using it_behaves_like to recall shared_examples is similar to using include_examples . However, in some cases it is not. According to the document, it reads:

include_examples “name” # include the examples in the current context

it_behaves_like “name” # include the examples in a nested context

To make this clearer, we will consider the following example:

If you call these 2 shared_examples consecutively by include_examples with different input params values ​​like:

If you run the test, you will see the first example fails. Reading log will see expected as ‘second value’, and current as ‘first value’, this means that something variable has received the last passed value. Map with docs above will understand: when using include_examples , the example will be added to the current context, while using it_behaves_like , the example will be wrapped in another context, then added to the current context. Therefore, when including 2 consecutive times like the above example, the variable something, in the same context, has been declared twice, resulting in overwriting, so the value received is the following declared value, is ‘second value’


The article carries my personal opinion, after a period of time rspec has written quite a lot. Looking forward to your contributions!

Share the news now

Source : Viblo