Ryder Bergerud Ryder Bergerud - 2 months ago 10
Ruby Question

Activerecord create method sometimes doesn't work in time

I'm having issues with the following code, where it only passes some of the time when I run it.

require_relative 'spec_helper'
require 'pry'

RSpec.describe Round do
testFruit = [Fruit.create(name: "Anjou Pear", unit: "10/LB", price: 13.24), Fruit.create(name: "Anjou Bear", unit: "10/LB", price: 15.24)]

before(:each) do |variable|
@round = Round.new
end

it 'returns all fruit in the current round of ordering' do
expect(@round.fruits).to match_array(testFruit)
end
it 'lets you clear the list for the next round' do
@round.next
expect(@round.fruits).to match_array([])
end
end


Where
@round.fruits
is defined as

def fruits
Fruits.all
end


So I understand for
Fruits.all
has to wait for the testFruits to be persisted the database, and I'm guessing this isn't done in time? Is there a way I can test this asynchronously with rspec, and should I be designing my tests differently to avoid this issue?

The error I get is
```
Failure/Error: expect(@round.fruits).to match_array(testFruit)

expected collection contained: [#<Fruit id: 53, pic: nil, description: nil, name: "Anjou Pear", unit: "10/LB", price: #<BigDecimal:2...l:206de28,'0.1524E2',18(27)>, created_at: "2016-09-27 18:18:23", updated_at: "2016-09-27 18:18:23">]
actual collection contained: []


```

Answer

Is there a way I can test this asynchronously with rspec

Yes, but I won't go into that here because it's not necessary if you write your test in a way more consistent with current best practices.

should I be designing my tests differently to avoid this issue?

Yes, the issue here is most likely due to what is referred to as "leaking state" between tests. The ideal is that every single test should be completely isolated from each test so that you can run your tests in any order and they will have the same outcome.

The problem is the testFruit array that you have just sitting in scope. If this Array or the objects it creates in the database are changed, that change is leaked into the tests that come afterwards.

I suggest instead writing your tests like this:

RSpec.describe Round do
  let!(:round) { Round.new }
  let!(:test_fruit) do
    [
      Fruit.create(name: "Anjou Pear", unit: "10/LB", price: 13.24),
      Fruit.create(name: "Anjou Bear", unit: "10/LB", price: 15.24)
    ]
  end

  describe "#fruits" do
    it 'returns all fruit in the current round of ordering' do
      expect(round.fruits).to match_array(testFruit)
    end
  end

  describe "#next" do
    it 'lets clears the list for the next round' do
      round.next

      expect(round.fruits).to match_array([])
    end
  end
end

This makes use of the let! functionality of RSpec, which you can read about here. Basically, it ensures that you are getting a consistent setup between each test.

Lastly, you don't have to worry about any async issues regarding database writes; Rails will not continue until it receives a successful reply from the database that the data was indeed written.