How can I share the factories that I have in a GEM

2020-02-09 03:43发布

问题:

I have a gem that includes some Factories. The gem looks something like:

.
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── db
├── lib
│   ├── models
│   │   ├── users.rb
├── pkg
├── core.gemspec
├── spec
│   ├── factories
│   │   └── users.rb
│   ├── fixtures
│   ├── helpers
│   ├── integration
│   ├── spec_helper.rb
│   ├── support│   │ 
│   └── unit
│       └── users_spec.rb
└── tasks

Now i'm using the gem in another Ruby project (Grape) by adding something like gem 'core', git: 'https://url.git'.

Now everything is working fine as I can use User model from Grape project.

However I want to use the factories (users) so I can write further integration tests for Grape project.

In Grape project, in spec_helper.rb it looks like:

require 'rubygems'
require 'bundler/setup'
Bundler.require(:default, :development)

ENV['RACK_ENV'] ||= 'test'

require 'rack/test'

require File.expand_path('../../config/environment', __FILE__)

RSpec.configure do |config|
  config.mock_with :rspec
  config.expect_with :rspec
  config.raise_errors_for_deprecations!
  config.include FactoryGirl::Syntax::Methods
end

require 'capybara/rspec'
Capybara.configure do |config|
  config.app = Test::App.new
  config.server_port = 9293
end

Now my test 'users_spec.rb' looks like:

require 'spec_helper'

describe App::UsersController do
  include Rack::Test::Methods

  def app
    App::API
  end

  describe "/users/me" do
    context "with invalid access token" do
      before(:each) do
        get "/api/v2/users/me"
        user = build(:user)
      end      

      it 'returns 401 error code' do
        expect(last_response.status).to eq(401)
        expect(user).to eq(nil)
      end
    end    
  end
end

Now when I try to run the test using rspec spec/api/users_spec.rb I get :

I keep getting this error:

 Failure/Error: user = build(:user)
 ArgumentError:
   Factory not registered: user

Any help would be appreciated as I've been struggling for this.

回答1:

An alternative to require-ing each factory file as suggested in the other answer, is to update the FactoryBot.definition_file_paths configuration.

In your gem defining the factories:

Create a file which will resolve the factory path:

# lib/my_gem/test_support.rb

module MyGem
  module TestSupport
    FACTORY_PATH = File.expand_path("../../spec/factories", __dir__)
  end
end

In you app / gem using the factories from the other gem:

# spec/spec_helper.rb or similar

require "my_gem/test_support"

FactoryBot.definition_file_paths = [
  MyGem::TestSupport::FACTORY_PATH,
  # Any other paths you want to add e.g.
  # Rails.root.join("spec", "factories")
]

FactoryBot.find_definitions

The advantage of the definition_file_paths solution is that other functionality like FactoryBot.reload will work as intended.



回答2:

The problem is that you probably don't expose the spec folder (and herewith the factories) in the load path. Which, in general, is the right thing to do. Check you *.gemspec, you probably have something like:

s.require_paths = ["lib"]

This means only files under the lib directory can be required by other projects using your gem. See http://guides.rubygems.org/specification-reference/#require_paths=

So to solve your problem, you'd need to place a file inside the lib folder which 'knowns' where your factories are and requires those. So in you case, create a file lib/<your gem name>/factories.rb and add:

GEM_ROOT = File.dirname(File.dirname(File.dirname(__FILE__)))

Dir[File.join(GEM_ROOT, 'spec', 'factories', '*.rb')].each { |file| require(file) }

In the other Project load the factories with:

require '<your gem name>/factories'

Works fine for me. The only thing I havn't figured out yet is how to namespace your factories. Not sure if factory girl allows this.