Filip Bartuzi
Filip Bartuzi

Reputation: 5931

How to DRY up a test case repeated in multiple contexts?

I monkeypatched URI and wrote tests for it:

describe URI do
  describe '.sanitize_domain' do
    let(:expectation) { 'toolbar.google.com' }
    subject { described_class.sanitize_domain(url) }

    context 'when url is correct' do
      context 'when url misses both http and www prefixes' do
        let(:url) { 'toolbar.google.com/public' }
        it { is_expected.to eq(expectation) }
      end

      context 'when url contains www' do
        let(:url) { 'www.toolbar.google.com/public' }
        it { is_expected.to eq(expectation) }
      end

      context 'when url contains https' do
        let(:url) { 'https://toolbar.google.com/public' }
        it { is_expected.to eq(expectation) }
      end

      context 'when url contains both http and www' do
        let(:url) { 'http://www.toolbar.google.com/public' }
        it { is_expected.to eq(expectation) }
      end

      context 'when multiple urls are given' do
        let(:url) { 'http://www.toolbar.google.com/Default.asp, http://www.toolbar.google.com/' }
        it { is_expected.to eq(expectation) }
      end
    end
  end
end

As you can see, it { is_expected.to eq(expectation) } is repeated in each context.

I think there is a solution to dry it up a bit. I could use shared_examples here, but IMHO it would be overkill to do that for a single it and readability would decrease.

I tried to extract expectation to before(:context) { it { is_expected } } but the it keyword isn't available inside before block. Here is the error message from that attempt:

it is not available from within an example (e.g. an it block) or from constructs that run in the scope of an example (e.g. before, let, etc). It is only available on an example group (e.g. a describe or context block).

Upvotes: 3

Views: 480

Answers (2)

Dave Schweisguth
Dave Schweisguth

Reputation: 37607

For tests that are both minimal and readable, just extract a method:

describe URI do
  describe '.sanitize_domain' do
    context 'when url is correct' do
      it 'sanitizes a URL without http and www prefixes' do
        expect_sanitized_domain_to_handle 'toolbar.google.com/public'
      end

      it 'sanitizes a URL which contains www' do
        expect_sanitized_domain_to_handle 'www.toolbar.google.com/public'
      end

      it 'sanitizes a URL which contains https' do
        expect_sanitized_domain_to_handle 'https://toolbar.google.com/public'
      end

      it 'sanitizes a URL which contains both http and www' do
        expect_sanitized_domain_to_handle 'https://www.toolbar.google.com/public'
      end

      it 'sanitizes a string containing multiple URLs' do
        expect_sanitized_domain_to_handle 'http://www.toolbar.google.com/Default.asp, http://www.toolbar.google.com/'
      end

      def expect_sanitized_domain_to_handle(url)
        expect(URL.sanitize_domain(url)).to eq('toolbar.google.com')
      end

    end
  end
end

Or, to get the expected return value into the test where you can see it, and change it if you need a test with a different return value:

it 'sanitizes a URL from a different site' do
  expect_sanitized_domain given: 'http://stackoverflow.com/questions/32331374/the-same-test-case-for-few-contexts-in-rspec-how-to-dry-it',
    to_return: 'stackoverflow.com'
end

def expect_sanitized_domain(given:, to_return:)
  expect(URL.sanitize_domain(given)).to eq(to_return)
end

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

inner_contextes_urls = [
  ['when url misses both http and www prefixes', 'toolbar.google.com/public'],
  ['when url contains www', 'www.toolbar.google.com/public']
  # ...
]
inner_contextes_urls.each do |(title, url)|
  context "#{title}" do
    let(:url) { "#{url}" }
    it { is_expected.to eq(expectation) }
  end
end

Upvotes: 1

Related Questions