# RSpec - Use shared_examples to avoid duplicate test examples

It's common to see a `before_action` method that need executing in different controller actions. At the same time, when writing RSpec tests for these actions, it's easily to write down context blocks whose test examples are almost the same, and only a `let` declaration is different.

The code might look like this:

%[https://gist.github.com/lynnbright/71ac3641db8d7dd33ef14d98ed1fa449] 

Though these test examples seem readable, there are some shortcomings:

1. When the rule of `#setup_discount` was changed, we need to revise `when assigns @discount` test example groups of `FruitContrller#index` and `FruitController#show` at the same time. Imagine when there are 10 controllers and all of them also need to execute `#setup_discount` which means we have to revise the same thing 10 times. horrible...
    
2. It seems not DRY.
    

Fortunately, RSpec provides `shared_examples` for us to solve the duplicate test examples problem.

---

### How to use `shared_examples`

Take `FruitContrller#index` and `FruitController#show` RSpec tests for example.

1. Add a new shared\_examples file.
    

Before adding this new file, we need to check where to put this file so that it will be required automatically before running tests.

In my project, I follow the convention and put this file at:

`spec/support/controllers/shared_examples/expected_discount_examples.rb`

```ruby
# spec/spec_helper.rb

Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
```

2\. Pick up the duplicate part.

For example,

```ruby
# index request test examples
context 'when assigns @discount' do
  let(:super_market) { create(:super_market) }
  let(:user) { create(:user, special_pass_available: special_pass_available? }
      
  #...
      
  context 'when current_user owns special pass' do     
    let(:special_pass_available?) { true }
    it { expect(assigns(:discount)).to eq(0.5) }
  end
      
  context 'when current_user does not own any special pass' do
    let(:special_pass_available?) { false }
    
    it { expect(assigns(:discount)).to eq(0) }
  end
end

# show request test examples
context 'when assigns @discount' do
  let(:super_market) { create(:super_market) }
  let(:user) { create(:user, special_pass_available: special_pass_available? }
      
  #...
      
  context 'when current_user owns special pass' do        
    let(:special_pass_available?) { true }
    
    it { expect(assigns(:discount)).to eq(0.5) }
  end
      
  context 'when current_user does not own any special pass' do
    let(:special_pass_available?) { false }
    
    it { expect(assigns(:discount)).to eq(0) }
  end
end
```

3\. Pick up the different parts.

For example,

```ruby
# index request test examples
context 'when assigns @discount' do
  #...
  
  before { index_request }
      
  #...
end

# show request test examples
context 'when assigns @discount' do
  #...
  
  before { show_request }
      
  #...
end
```

4\. Put the duplicate part in the shared\_examples file.

```ruby
RSpec.shared_examples 'an expected @fruit' do
  let(:super_market) { create(:super_market) }
  let(:user) { create(:user, special_pass_available: special_pass_available? }
  
  #...
      
  context 'when current_user owns special pass' do  
    let(:special_pass_available?) { true }
    
    it { expect(assigns(:discount)).to eq(0.5) }
  end
      
  context 'when current_user does not own any special pass' do
    let(:special_pass_available?) { false }
    
    it { expect(assigns(:discount)).to eq(0) }
  end
end
```

5\. Setup `request` variable for the different part

```ruby
RSpec.shared_examples 'an expected @fruit' do
  #...
      
  before { request }
      
  #...
end
```

6\. Include the shared\_examples into the test file.

RSpec [official documentation](https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples) provides four ways to include:

```ruby
include_examples "name"      # include the examples in the current context
it_behaves_like "name"       # include the examples in a nested context
it_should_behave_like "name" # include the examples in a nested context
matching metadata            # include the examples in the current context
```

I need to pass different request actions which are declared in `subject` or `let` into the shared group, so I use `it_behaves_like` to include it.

&lt;!--kg-card-begin: html--&gt; &lt;script src="https://gist.github.com/lynnbright/aadb8c014ce5eca30a9a2a721e3c4cab.js"&gt;&lt;/script&gt; &lt;!--kg-card-end: html--&gt;

After using `shared_examples`, we solve two problems:

* When the rule of `#setup_discount` changes, all we need to do is to revise the shared\_examples file and make a minor adjustment to `let` declaration.
    
* It seems DRY now.
    

---

Although `include_examples` didn't come in handy this time, there is something worth noting.

According to the official documentation, there is a warning message:

> **WARNING:** When you include parameterized examples in the current context multiple times, you may override previous method definitions and last declaration wins.

> To prevent this kind of subtle error a warning is emitted if you declare multiple methods with the same name in the same context. Should you get this warning the simplest solution is to replace `include_examples` with `it_behaves_like`, **in this way method overriding is avoided because of the nested context created by** `it_behaves_like`

Read the detailed code sample: [here](https://relishapp.com/rspec/rspec-core/v/3-10/docs/example-groups/shared-examples#aliasing-%60it-should-behave-like%60-to-%60it-has-behavior%60)

---

### Reference

* [https://relishapp.com/rspec/rspec-core/v/3-10/docs/example-groups/shared-examples](https://relishapp.com/rspec/rspec-core/v/3-10/docs/example-groups/shared-examples)
    
* [https://github.com/rubocop/rspec-style-guide#shared-examples](https://github.com/rubocop/rspec-style-guide#shared-examples)
    
* [https://www.betterspecs.org/#shared](https://www.betterspecs.org/#shared)
