FactoryBot how to access same var in multiple factories

468 views Asked by At

I would like to know how to use the same array in multiple factories using rspec in Rail, does any body know a good solution?

Currently, I have the array bellow that I have to repeat in every factory that needs to enter the lat/log.

coordinates = [
  {latitude: 50.0, longitude: -100.0},
  {latitude: 50.1, longitude: -100.1},
  {latitude: 50.2, longitude: -100.2},
  {latitude: 50.3, longitude: -100.3}
]

Then, to attribute a value I do a .sample and insert into the field:

coordinate = coordinates.sample
latitude { coordinate[:latitude] }
longitude { coordinate[:longitude] }

I would like to keep these coordinates "padronized" so I can be sure where it is going to be. That's because later down the road I check if they fall inside a polygon or not.

I am currently using: rails 5.2.3 rspec-rails 3.8.0 factory_bot_rails 5.0.1

2

There are 2 answers

0
Clemens Kofler On

If you have any data that you don't want to be changed, the simplest approach is to basically treat it as fixtures. You can either use Rails' own fixtures mechanism (see documentation in the official Rails Guides: https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures) or build your own simple fixture importer:

# spec/fixtures/coordinates.yml
- latitude: 50.0
  longitude: -100.0
- latitude: 50.1
  longitude: -100.1
- latitude: 50.2
  longitude: -100.2
- latitude: 50.3
  longitude: -100.3
# spec/support/fixtures_helper

module FixturesHelper
  def coordinates
    YAML.load_file(Rails.root.join('spec', 'fixtures', 'coordinates.yml'))
  end
end

RSpec.configure do |config|
  config.include FixturesHelper
end
# in some test

it 'does something with coordinates' do
  expect(do_something_with(coordinates[0]).to eq some_result
end
1
Schwern On

I would like to keep these coordinates "padronized" so I can be sure where it is going to be. That's because later down the road I check if they fall inside a polygon or not.

FactoryBot is about avoiding having fixed testing data. Instead, write a factory which will generate valid coordinates.

FactoryBot.define do
  factory :coordinates, class: "Hash" do
    latitude { rand(-180.0..180.0) }
    longitude { rand(-180.0..180.0) }

    initialize_with do
      attributes
    end
  end
end

Rather than hard coding your coordinates so they fit inside a hard coded polygon, generate a polygon which contains the coordinates as you need. Do this with a trait. Traits let different tests use the same factory in different ways without any risk of interfering with each other.

factory :polygon do
  # normal polygon attributes go here

  trait :contains_coordinates do
    transient do
      coordinates { build(:coordinates) }
    end
      
    # set up the attributes so they contain the coordinate
  end
end

Then ask for a polygon which contains a coordinate when you need it.

describe '#contains_coordinates?' do
  subject {
    polygon.contains_coordinates?(coordinates)
  }

  context 'when the coordinates are inside the polygon' do
    let(:coordinates) { build(:coordinates) }
    let(:polygon) {
      build(:polygon, :contains_coordinates, coordinates: coordinates)
    }

    it { is_expected.to be true }
  end
end

This can also work the other way around, add a trait to the coordinates factory which is guaranteed to be inside a polygon.