Home > database >  Rspec - Many examples one context
Rspec - Many examples one context

Time:07-28

I have just finished reading Testing Rails guide by thoughtbot. The author mentions that using let! or let is an antipattern because in your test you dont get to see the variables that are needed for the setup.

I have this one common case that I'm curious how to deal with however if lets would be removed. I'm creating tests for creating a cart. When a cart is created, many things are tested. The current test is as follows (I removed the implementation of some methods and just kept the structure for brevity):

RSpec.describe Mutations::Carts::CreateCart, type: :request do
    context 'Sending a request with an unauthorized user' do
      it 'Creates a new cart and set the table for it' do
        # Test setup
        store = create :store
        branch = store.branches.first
        table = create :table, branch: branch
        create_dependencies_for_create_cart(store)

        cart_count_before_request = Cart.count

        # Exercise
        send_create_cart(store.id, table.id)

        # Verify: (Some expects)
      end

      it 'Returns the branch id and and the table id' do
        # Test setup
        store = create :store
        branch = store.branches.first
        table = create :table, branch: branch
        create_dependencies_for_create_cart(store)

        # Exercise
        send_create_cart(store.id, table.id)

        # Verify: Some expects
      end
  end

  private

  def send_create_cart(store_id, table_id)
    # Prepare and send the request
  end


  def create_dependencies_for_create_cart(store)
     # Creating some objects that need to be created before the cart is created
  end
end

My problem here is that the test setup is common and needs to only run once. Also the request can be sent once. One tiny improvement over this would be to add a before(:each). This would on the one hand DRY the code a bit, but on the other hand, I'm still running the setup exercise twice potentially slowing my tests down, and I'm making the test less readable. before(:all) would at least ensure that the setup and exercise are only run once, still this is deperacted and recommended against by the Ruby community.

What's the clean and effecient way (both readable and requires no repeated setups) to test such cases?

CodePudding user response:

A good approach would be using a describe with a context with a different describes inside it each having its own before do.

describe 'Sending a request' do
 context ' with an unauthorized user' do
  store = create :restaurant
  branch = store.branches.first
  table = create :table, branch: branch
  describe 'creating cart and table' do
    before do
      create_dependencies(store)

      cart_count_before_request = Cart.count

      # Exercise
      send_create_cart()
    end
    it 'should not create a cart' do
      # Verify: (Some expects)
    end

    it 'returns the ids'do
      # Verify: Some expects
    end
  end
 end
end

CodePudding user response:

All create should be set via let. Keep in mind that all your specs must be independent all the time. No matter the order they have been run. If you've a test that checks if a new cart has be created. That's one test. Another one might be to check the response.

Even if best practices say it's better to have a single expect per spec, sometime it makes sense to have several. It's all about context and logical for other dev to debug your code later on.

Here is my proposition:

context 'Sending a request with an unauthorized user' do
  subject { send_create_cart(store.id, table.id) }

  let(:store) { create(:store) }
  let(:branch) { create(:branch, store: store)} # check according to your association
  let!(:table) { create(:table, branch: branch) }
  # Create dependencies for cart here, on `let` too.
  # use `!` to force creation
  # Here only `table` has `!` since other variable are called to create this one.

  it 'Creates a new cart and set the table for it' do
    expect{ subject }.to change { Cart.count }.by(1)
  end

  it 'Returns the branch id and and the table id' do
    expect(subject).to eq([branch.id, table.id])
  end

  private

  def send_create_cart(store_id, table_id)
    # Prepare and send the request
  end
end
  • Related