Collective Idea

Collective Idea Logo

Steve Richert

Factory Girl without Active Record

By Steve Richert on November 05, 2013 in factory girl

Factory Girl has been around for more than five years now and has become the standard for building and saving valid model data for your test suite. Out of the box, Factory Girl plays nicely with the major ORMs: Active Record, Mongoid, DataMapper and MongoMapper. But what about those pesky models that fall outside of your ORM? Fear not… Factory Girl’s got you covered there too!

Non-ORM Models

Developers often define their models around their database tables but as your application grows and becomes more interconnected, you will begin to pull data from sources outside your own database. Make no mistake: the Ruby objects that encapsulate this external data are still models, even without your ORM of choice.

I’ll share examples of two external models from an application I’ve been working on.

Facebook Auth

The application uses OmniAuth to register and authenticate users via Facebook. OmniAuth documentation describes the structure of the Auth Hash returned from Facebook. This is our external data. OmniAuth wraps this hash in a class called OmniAuth::AuthHash. That is our model.

When a user authenticates via Facebook, the application either finds an existing user or creates a new one from the auth hash that Facebook returned.

class User < ActiveRecord::Base
  def self.from_omniauth(facebook_auth)
    raw_info = facebook_auth.extra.raw_info

    find_or_create_by!(facebook_id: raw_info.id) do |user|
      user.first_name = raw_info.first_name
      user.last_name = raw_info.last_name
      user.email = raw_info.email
      user.gender = raw_info.gender
    end
  end
end

In testing this method, it would be really helpful to have a realistic auth hash to work with. Enter: Factory Girl.

FactoryGirl.define do
  factory :facebook_auth, class: OmniAuth::AuthHash do
    skip_create

    ignore do
      id { SecureRandom.random_number(1_000_000_000).to_s }
      name { "#{first_name} #{last_name}" }
      first_name "Joe"
      last_name "Bloggs"
      link { "http://www.facebook.com/#{username}" }
      username "jbloggs"
      location_id "123456789"
      location_name "Palo Alto, California"
      gender "male"
      email "joe@bloggs.com"
      timezone(-8)
      locale "en_US"
      verified true
      updated_time { SecureRandom.random_number(1.month).seconds.ago }
      token { SecureRandom.urlsafe_base64(100).delete("-_").first(100) }
      expires_at { SecureRandom.random_number(1.month).seconds.from_now }
    end

    provider "facebook"
    uid { id }

    info do
      {
        nickname: username,
        email: email,
        name: name,
        first_name: first_name,
        last_name: last_name,
        image: "http://graph.facebook.com/#{id}/picture?type=square",
        urls: { Facebook: link },
        location: location_name,
        verified: verified
      }
    end

    credentials do
      {
        token: token,
        expires_at: expires_at.to_i,
        expires: true
      }
    end

    extra do
      {
        raw_info: {
          id: uid,
          name: name,
          first_name: first_name,
          last_name: last_name,
          link: link,
          username: username,
          location: { id: location_id, name: location_name },
          gender: gender,
          email: email,
          timezone: timezone,
          locale: locale,
          verified: verified,
          updated_time: updated_time.strftime("%FT%T%z")
        }
      }
    end
  end
end

What makes this factory special is the skip_create method call and the ignore block.

The skip_create method does just what it says. Rather than using Factory Girl’s default behavior of trying to save the instance to the database, persistence is skipped.

The ignore block defines a list of attributes that won’t be passed into the new instance but can be used elsewhere in the factory. Ignored attributes are perfect for deeply nested structures like the auth hash factory above. I can specify a first_name and it will appear throughout the new auth hash.

facebook_auth = FactoryGirl.create(:facebook_auth, first_name: "Steve")

facebook_auth.info.first_name           # => "Steve"
facebook_auth.info.name                 # => "Steve Bloggs"
facebook_auth.extra.raw_info.first_name # => "Steve"
facebook_auth.extra.raw_info.name       # => "Steve Bloggs"

It’s incredibly handy to have a completely valid Facebook auth hash available from anywhere in your test suite. Plus, this factory should not need to change often, if at all.

Balanced Customer

Balanced is a payment processing company designed for marketplaces. They can process payments from customers and payouts to sellers. They have a robust API with its own Ruby wrapper. The API is our external data and the wrapper’s resource classes are our models.

Defining factories for these external resources is done similarly to the Facebook auth hash above and takes advantage of a couple more techniques: the initialize_with method and “traits.”

FactoryGirl.define do
  trait :balanced_resource do
    skip_create

    initialize_with do
      new.class.construct_from_response(attributes)
    end

    ignore do
      balanced_marketplace_uri { ENV["BALANCED_MARKETPLACE_URI"] }
    end

    id { SecureRandom.urlsafe_base64(24).delete("-_").first(24) }
  end

  factory :balanced_card, class: Balanced::Card do
    balanced_resource

    account nil
    brand "MasterCard"
    card_type "mastercard"
    country_code nil
    created_at { Time.current.xmlschema(6) }
    customer nil
    expiration_month 12
    expiration_year 2020
    hash { SecureRandom.hex(32) }
    is_valid true
    is_verified true
    last_four "5100"
    meta({})
    name nil
    postal_code nil
    postal_code_check "unknown"
    security_code_check "passed"
    street_address nil
    uri { "#{balanced_marketplace_uri}/cards/#{id}" }
  end

  factory :balanced_bank_account, class: Balanced::BankAccount do
    balanced_resource

    account_number "xxxxxx0001"
    bank_name "BANK OF AMERICA, N.A."
    can_debit false
    created_at { Time.current.xmlschema(6) }
    credits_uri { "#{uri}/credits" }
    customer nil
    debits_uri { "#{uri}/debits" }
    fingerprint { SecureRandom.hex(32) }
    meta({})
    name "Johann Bernoulli"
    routing_number "121000358"
    type "checking"
    uri { "/v1/bank_accounts/#{id}" }
    verification_uri nil
    verifications_uri { "#{uri}/verifications" }
  end
end

By default, Factory Girl passes its attributes hash to the new method. This works in most cases but unfortunately, not for our Balanced resources. They instead use the class method construct_from_response. Currently, getting access to a class method within the initialize_with block is awkward but can be done by calling the method on new.class.

You can see that we also registered a balanced_resource trait. A trait is a set of attributes and behaviors that can be reused across other factories. The balanced_resource trait sets up initialization, skips create and gives us some default attributes. The balanced_card and balanced_bank_account factories can then call that trait and inherit all of the Balanced-specific behavior.

One more thing…

Using Factory Girl for API-backed models like the Balanced models above allows you to go further and stub how the models find their instances.

Even though we skip_create in our Balanced factories, the after(:create) callback can still be fired. If we tap into that callback, we can stub the Balanced library’s find method to return the instance we just “created.”

factory :balanced_card, class: Balanced::Card do
  balanced_resource

  account nil
  # ...
  uri { "#{balanced_marketplace_uri}/cards/#{id}" }

  after(:create) do |card|
    card.class.stub(:find).with(uri).and_return(card)
  end
end

This approach can clean up your test suite and allow you to more easily write tests that don’t require a connection to your external services.


Check out our latest product, Dead Man’s Snitch for monitoring cron, heroku scheduler or any periodic task.

Dead Man's Snitch

By Steve Richert on November 05, 2013 in factory girl

5 Comments

  1. Steven Yap

    Steven Yap November 07, 2013 http://www.futureworkz.com

    This is really a great way to use FactoryGirl for non-ActiveRecord models. Thanks Steve!

  2. Dario

    Dario November 10, 2013 http://www.insignia4u.com

    Excellent, just what I was looking for! Thank you very much!

  3. Wayne

    Wayne January 07, 2014

    hello, i have some trouble about
    while i create a instance by FactoryGirl.create and try to access the attribute define in ignore block, i got
    NoMethodError: undefined method `info’
    What’s wrong?

    i just do something like this:
    FactoryGirl.define do
      factory :facebook_auth, class: MyAPI::Cls do
        skip_create
        ignore do
          id { SecureRandom.random_number(1_000_000_000).to_s }
        end
      end
    end

  4. Steve Richert

    Steve Richert January 07, 2014

    Wayne, ignored attributes are only available from within the factory definition. They are not actually set on the resulting instance. I hope that helps!

  5. Sean Wolfe

    Sean Wolfe February 21, 2014

    This still doesn’t work if you want to use the stub strategy. It still attempts to pretend to save your model, and set an :id value. This is useless if your custom model doesn’t have an :id field.

Post a Comment

Contact Us

Find us on Google Maps
Collective Idea
44 East 8th Street, Suite 410
Holland, Michigan 49423 USA 42.790334-86.105251

Follow us on the Interwebs

We are currently available for medium and long term projects. Please get in touch if we can be of service.