ole
ole

Reputation: 5233

Rspec: shared examples

I have feature test, which have 2 duplicated parts:

require 'spec_helper'

describe "Messages manage" do
  let (:user) { create :user }
  let (:other_user) { create :use }
  before(:each) do
    login_as user
  end

  describe "[message create]" do

    it "messages created by self, should appear on the page", js: true do
      message_text = "Hello my friend"

      visit user_messages_path(user, {with:other_user.id} )
      fill_in :message_body, with: message_text
      click_button t("users.messages.index.send")
      sleep 1 # waiting for js

      page.should have_content(message_text)
      find_field(:message_body).value.should == ""
      page.should have_selector('#message_body', visible: false)
    end

    it "message errors should be displayed", js: true do
      message_text = "123"

      visit user_messages_path(user, {with:other_user.id} )
      fill_in :message_body, with: message_text
      click_button t("users.messages.index.send")
      sleep 1 # waiting for js

      page.should_not have_content("Message: #{message_text}")
      find_field(:message_body).value.should == message_text
      page.should have_selector('.message.error', visible: true)
    end

  end
end

What is the best way using of shared_examples or something like that, in this situation? To following DRY.

Upvotes: 3

Views: 1053

Answers (2)

Paul Fioravanti
Paul Fioravanti

Reputation: 16793

I don't think you have quite enough similar code to necessitate a shared_examples_for block (though an argument could be made for a utility method in spec/support/utilities.rb), so instead I attempted to split it out a bit.

It's difficult to quantitatively give you a right answer for this question as a lot depends on your style of coding, as per the discussion around @BillyChan's answer. My style is to attempt to be as curt yet readable as possible; others may think it's too DRY, some not DRY enough. Don't know if this actually works as I don't have your code, but for your consideration...

require 'spec_helper'

describe "Message management" do

  let(:user)       { create :user }
  let(:other_user) { create :user }

  before { login_as user }

  subject { page }

  describe "message creation" do
    let(:send_button) { t("users.messages.index.send") }

    before { visit user_messages_path(user, { with: other_user.id }) }

    context "by self", js: true do
      let(:message_text) { "Hello my friend" }

      before do
        fill_in :message_body, with: message_text
        click_button send_button
        sleep 1 # waiting for js
      end

      it { should have_content(message_text) }
      it { should have_selector('#message_body', visible: false) }
      specify { find_field(:message_body).value.should == "" }
    end

    describe "message errors", js: true do
      let(:message_text) { "123" }

      before do
        fill_in :message_body, with: message_text
        click_button send_button
        sleep 1 # waiting for js
      end

      it { should_not have_content("Message: #{message_text}") }
      it { should have_selector('.message.error', visible: true) }
      specify { find_field(:message_body).value.should == message_text }
    end
  end        
end

Edit Further explanation as requested by @ole:

specify is an alias for it. I use specify here because I think the phrase reads better (you could just as easily substitute it out for it). Also, the conditions in the it blocks relate to the subject of page, while the content in the specify block is essentially not referring directly to the page and is "changing the subject" of the test. If I wanted to put the code from the specify blocks into what I think is a readable, though a bit more verbose, it block, I'd probably change it to something like:

describe "message errors", js: true do
  let(:message_text) { "123" }

  before do
    fill_in :message_body, with: message_text
    click_button send_button
    sleep 1 # waiting for js
  end

  it { should_not have_content("Message: #{message_text}") }
  it { should have_selector('.message.error', visible: true) }

  describe "message body value" do
    let(:message_body) { find_field(:message_body).value }
    subject { message_body }
    it { should == message_text }
  end
end

You could also remove the let statement and put find_field(:message_body).value directly into the subject block if you wanted. It's all a matter of taste and style. Is this syntax appropriate for this example? You decide :-)

Upvotes: 2

Billy Chan
Billy Chan

Reputation: 24815

You can append a specific method right under the code

require 'spec_helper'

describe "Messages manage" do
  # let (:user) { create :user } 
  # Instead expose it as instance variable
  @user = create(:user)
  # let (:other_user) { create :user }
  @other_user = create(:user)
  before(:each) do
    login_as user
  end

  describe "[message create]" do

    it "messages created by self, should appear on the page", js: true do

      # Use the custom method
      send_message "Hello my friend"

      page.should have_content(message_text)
      find_field(:message_body).value.should == ""
      page.should have_selector('#message_body', visible: false)
    end

    it "message errors should be displayed", js: true do

      # Use the custom method
      send_message "123"

      page.should_not have_content("Message: #{message_text}")
      find_field(:message_body).value.should == message_text
      page.should have_selector('.message.error', visible: true)
    end

  end


  def send_message(message_text)
        visit user_messages_path(@user, {with:@other_user.id} )
        fill_in :message_body, with: message_text
        click_button t("@users.messages.index.send")
        sleep 1 # waiting for js
  end

end

For more general code, you can extract them into spec/support in a module. But the above method should be good enough for this case.

Upvotes: 3

Related Questions