How can I share the factories that I have in a GEM and use it in other project?
Asked Answered
M

2

21

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.

Microphyte answered 29/7, 2015 at 12:25 Comment(2)
Ahh! good to see some specs,, :)Asthma
Although it may be possible. It's questionable if you should share the specs. You should only use what the gem provides. And the specs should be for your application so you shouldn't need the factories.Libratory
K
11

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.

Kincaid answered 10/5, 2018 at 11:26 Comment(0)
L
13

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.

Lalo answered 10/9, 2015 at 10:40 Comment(2)
Dir[File.join(GEM_ROOT, 'spec', 'factories', '**','*.rb')].each { |file| require(file) } This will include factories in subfolders as wellAekerly
GEM_ROOT = Gem.loaded_specs[gem_name].full_gem_pathRidicule
K
11

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.

Kincaid answered 10/5, 2018 at 11:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.