Can I use mysql database with Factory Girl at the test environment, how to do that?

I try to change my database from sqlite to mysql.

However, after I modify my database.yml from sqlite to mysql, my test fail which the test, “/spec/requests/apis/v1/cats_controller_spec.rb”, was fine, when the database was sqlite.

So I setup test environment to use sqlite, and production, and development environment to use mysql, and my test works again.

The error message is following, when I setup the database to mysql.

"ActiveRecord::RecordNotFound"

Can I use mysql database with Factory Girl at the test environment, how to do that?

My database.yml is following, when I setup mysql at test environment.

# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: mysql2
  encoding: utf8
  database: development_test_api
  pool: 5
  username: root
  password: "123456"
  socket: /tmp/mysql.sock

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: mysql2
  encoding: utf8
  database: test_test_api
  pool: 5
  username: root
  password: "123456"
  socket: /tmp/mysql.sock

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

My test, “/spec/requests/apis/v1/cats_controller_spec.rb” is following.

require 'spec_helper'

describe 'Cat management' do
  it 'returns a list of cats' do
    # create a cat in the database with name Persian
    FactoryGirl.create(:cat, name: 'Persian')
    FactoryGirl.create(:cat, name: 'Garfield')

    # make a get request to the cats index (note the format json)
    get '/apis/v1/cats', format: 'json'

    # parse the json into ruby
    result = JSON.load(response.body)

    # asert that the cat returned from the API has the name Persian
    # note it's an array of hashes so i'm using ".first" on the array
    expect(result.first['name']).to eq 'Persian'
    expect(result.last['name']).to eq 'Garfield'
  end

  it 'show a cat' do
    FactoryGirl.create(:cat, name: 'Persian')

    get '/apis/v1/cats/1', format: 'json'

    result = JSON.load(response.body)

    expect(result['name']).to eq 'Persian'
  end

  it 'creates a cat' do
    post '/apis/v1/cats', :cat => {:name => 'Persian'}, format: 'json'

    result = JSON.load(response.body)

    expect(result['name']).to eq 'Persian'
    expect(response.status).to eq 201
  end

  it 'update the name of a cat' do
    FactoryGirl.create(:cat, name: 'Persian')

    put '/apis/v1/cats/1', :cat => {:name => 'Garfield'}, format: 'json'

    result = JSON.load(response.body)

    expect(result['name']).to eq 'Garfield'
    expect(response.status).to eq 200
  end

  it 'destroy a cat record' do
    FactoryGirl.create(:cat, name: 'Persian')

    delete '/apis/v1/cats/1', format: 'json'

    result = JSON.load(response.body)

    expect(result['name']).to eq 'Persian'
    expect(response.status).to eq 200
  end

end

The error messages, when I setup mysql as my test environment database.

Cat management
  returns a list of cats
  show a cat (FAILED - 1)
  update the name of a cat (FAILED - 2)
  destroy a cat record (FAILED - 3)
  creates a cat

Failures:

  1) Cat management show a cat
     Failure/Error: get '/apis/v1/cats/1', format: 'json'
     ActiveRecord::RecordNotFound:
       Couldn't find Cat with id=1
     # ./app/controllers/apis/v1/cats_controller.rb:11:in `show'
     # ./spec/requests/apis/v1/cats_controller_spec.rb:24:in `block (2 levels) in <top (required)>'

  2) Cat management update the name of a cat
     Failure/Error: put '/apis/v1/cats/1', :cat => {:name => 'Garfield'}, format: 'json'
     ActiveRecord::RecordNotFound:
       Couldn't find Cat with id=1
     # ./app/controllers/apis/v1/cats_controller.rb:34:in `update'
     # ./spec/requests/apis/v1/cats_controller_spec.rb:43:in `block (2 levels) in <top (required)>'

  3) Cat management destroy a cat record
     Failure/Error: delete '/apis/v1/cats/1', format: 'json'
     ActiveRecord::RecordNotFound:
       Couldn't find Cat with id=1
     # ./app/controllers/apis/v1/cats_controller.rb:46:in `destroy'
     # ./spec/requests/apis/v1/cats_controller_spec.rb:54:in `block (2 levels) in <top (required)>'

Finished in 0.10862 seconds
5 examples, 3 failures

Failed examples:

rspec ./spec/requests/apis/v1/cats_controller_spec.rb:21 # Cat management show a cat
rspec ./spec/requests/apis/v1/cats_controller_spec.rb:40 # Cat management update the name of a cat
rspec ./spec/requests/apis/v1/cats_controller_spec.rb:51 # Cat management destroy a cat record

Randomized with seed 35843

My source codes is following.

my source codes

Many thanks for reply.

Hi @vincentlin,

The root of the problem is that you’re always assuming the ID will be 1 in your tests.

Every example in your spec file is wrapped in a database transaction (there’s more information about transactions in the rspec-rails documentation). When the test is finished the transaction is rolled back, restoring the database to the state it was in before the transaction started. Exactly what is restored varies from database to database: In SQLite the rollback resets the next value of the auto-incrementing ID field, but in MySQL it does not. The outcome of this is that when you run your tests with SQLite, the IDs of the objects you create in each test start over at 1 each time, but when you run the tests with MySQL the IDs continue to count up throughout the whole test run.

You can get around this difference by using the ID of the created cat in each test, for example:

it 'show a cat' do
  cat = FactoryGirl.create(:cat, name: 'Persian')

  get "/apis/v1/cats/#{cat.id}", format: 'json'

  result = JSON.load(response.body)
  expect(result['name']).to eq 'Persian'
end

Hope that helps!

1 Like

Hi georgebrock,

Many thanks for your reply.

You are right, and after refactoring the codes, it works great.

Thank you very much.
Vincent