Blog

Thoughts from my daily grind

Introduction to RSpec shared examples

Posted by Ziyan Junaideen |Published: 15 April 2023 |Category: Ruby on Rails
Default Upload |

As a Ruby developer, I often implement classes with inheritance or modules (and concerns in the case of Rails) to implement shared logic. We need to test each extension to make sure that it behaves as we intend it to, and when doing so, RSpec shared examples to help us keep the testing DRY.

Creating shared examples

We can define shared examples within an RSpec file (if it is only used in the particular RSpec test, or a general location loaded by RSpec. Here will use the shared_examples method to define a shared example and include_examples method to include it.

# ./spec/support/shared_examples/acts_as_inbound_webhook_endpoint.rb
shared_examples 'acts as an inbound webhook endpoint' do |action = :create|
  subject { post :create, body: data.to_json }

  let(:data) { { 'foo' => 'bar' } }

  it 'records the inbound webhook' do
    expect { subject }.to change(InboundWebhook, :count).by(1)

    webhook = InboundWebhook.recent.first
    expect(webhook.body).to eq(data)
  end
end

Then we can use them in the code as follows:

include_examples 'acts as an inbound webhook endpoint'

Customisng shared examples

It is in the natue of any thing shared that you eventually need some sort of customization. In the case of shared examples, RSpec allows us to pass in arguments to the shared example block.

Accepting Arguments

shared_examples 'acts as an inbound webhook endpoint' do |action = :create|
  subject { post action, body: data.to_json }

  let(:data) { { 'foo' => 'bar' } }

  it 'records the inbound webhook' do
    expect { subject }.to change(InboundWebhook, :count).by(1)

    webhook = InboundWebhook.recent.first
    expect(webhook.body).to eq(data)
  end
end

Then we can use them in the code as follows:

include_examples 'acts as an inbound webhook endpoint', :update

Overriding let definitions

Another way we can override RSpec shared examples is by means of overriding let definitions by introducing a block when using include_examples. Using the above example, should we need to override let(:data), we could:

include_examples 'acts as an inbound webhook endpoint' do
  let(:data) { { 'bar' => 'baz' } }
end
About the Author

Ziyan Junaideen -

Ziyan is an expert Ruby on Rails web developer with 8 years of experience specializing in SaaS applications. He spends his free time he writes blogs, drawing on his iPad, shoots photos.

Comments