Search code examples
ruby-on-railsrubyrspecrspec-rails

Using RSpec shared example groups with belongs_to associations


In a Rails project (rails 5.2.2, ruby 2.4.1) I have 2 resources defined, one named "groups" and one named "products". Product objects have a belongs_to relationship with a Group. I wanted to created an rspec shared example group which can test both resources, but I'm having some trouble with the "create" and "update" actions for "products".

I wanted to setup a shared set of example groups which take a hash used to create a new record as a parameter. The example group could then be called within both the "groups_spec.rb" and the "products_spec.rb". I have fixtures for both "groups" and "products. The following is a code example for the "requests/products_spec.rb" which calls the shared examples:

RSpec.describe "Products", type: :request do
   fixtures :groups, :products
   it_should_behave_like("modify data checks",
         Rails.application.routes.url_helpers.api_products_path,
         Product,
         { product: {
            name: "New Product",
            description: "Test product to add or modify",
            group_id: Group.first.id,
            label: "NP"
         } })
   end
end

The problem with products is that the new product data requires a group_id which must be valid inside the context of the example group, but I have to be able to retrieve the group_id from outside the example group in order to pass it in.

I'm guessing the real answer is to reorganize how the example group is structured, so I'll take a recommendation on how to restructure the shared example group. Of course, if I'm simply just doing something wrong here, I'll take that answer too.


Solution

  • I use a let inside of the it_behaves_like block like below:

    RSpec.describe "Products", type: :request do
      fixtures :groups, :products
    
      # This shared example probably lives in another file, which is fine
      # I don't usually pass in args, instead using everything via `let`
      shared_example 'modify_data_checks' do
        # you have access to all your variables via let inside here
        before do
          visit(path)
        end
    
        expect(model).to be_a(Product)
        expect(group_id).to eq(1)
      end
    
      it_behaves_like 'modify_data_checks' do
        let(:path) { Rails.application.routes.url_helpers.api_products_path }
        let(:model) { Product }
        let(:group_id) { Group.first.id }
        let(:params) { 
          product: {
            name: "New Product",
            description: "Test product to add or modify",
            group_id: group_id,
            label: "NP"
          }
        }
      end
    end
    

    You should be able to pass data around relatively cleanly like this. We're using a pattern similar to this to test polymorphic relationships using shared examples.