On Monday, May 18, 2015 at 10:15:30 AM UTC-7, [email protected] wrote:
>
> I'm working in a project which involves ruby, sequel and sinatra. I read 
> about which testing framework to use, and RSpec seems to be the most used 
> by the community.
>
> The project consists in a CRUD application, using DAO as the persistence 
> pattern.
>     
>     require 'sequel'
>
>     DB = Sequel.sqlite
>     DB.create_table :foos do
>       primary_key :id
>       int :foo_attribute1
>       int :foo_attribute2
>     end
>
>     class Foo
>       attr_accessor :id, :foo_attribute1, :foo_attribute2
>     end
>
>     module FooDAO
>       extend self
>
>       def save(f)
>         DB[:foos].insert(foo_attribute1: f.foo_attribute1, foo_attribute2: 
> f.foo_attribute2)
>       end
>
>       def [](id)
>         DB[:foos].where(id: id).first
>       end
>
>       def update(f)
>         DB[:foos].where(id: f.id).update(foo_attribute1: 
> f.foo_attribute1, foo_attribute2: f.foo_attribute2)
>       end
>
>       def count
>         DB[:foos].count
>       end
>     end
>
>     describe FooDAO do
>       context 'save' do
>         f = Foo.new
>         f.foo_attribute1 = 1
>         f.foo_attribute2 = 2
>         FooDAO.save f
>         it { expect(FooDAO.count).to eq 1 }
>       end
>
>       context 'get' do
>         it { expect(FooDAO[1][:foo_attribute1]).to eq 1 }
>         it { expect(FooDAO[1][:foo_attribute2]).to eq 2 }
>       end
>
>       context 'update' do
>         f = Foo.new
>         f.id = 1
>         f.foo_attribute1 = 2
>         f.foo_attribute2 = 3
>         FooDAO.update f
>         it { expect(FooDAO[1][:foo_attribute1]).to eq 2 }
>         it { expect(FooDAO[1][:foo_attribute2]).to eq 3 }
>       end
>     end
>
> In this case, the context `get` won't pass the examples. But if I comment 
> the context `update`, they will.
>

The problem is that your tests are not written to be independent. It’s 
really, really important that each test is able to pass when run 
individually and also pass when all the specs are run, in any order. RSpec 
supports random ordering (--order random) to help surface cases where 
you’ve failed to do this. I recommend you use it.

In your specific case, a big part of the problem is that you’re creating 
and mutating the DB records directly in your context blocks. context blocks 
are not designed for this. They provide a way to group multiple examples, 
provide helper methods, perform common setup via a before hook, etc, but 
you don’t want to directly manipulate the objects you are testing in a 
context block — instead move that into a before hook or helper method. When 
RSpec runs, it loads your spec file and evaluates all your describe and 
context blocks but *does not* run the examples (the it blocks), the hooks, 
or any let declarations. Instead, it stores them for later use. After 
loading all the spec files, RSpec applies any filtering, ordering, etc to 
the examples, and then runs them. That means that in your case, this is 
what’s happening:

   1. The ‘save’ context is evaluated, causing a record to be saved with 
   particular attributes.
   2. The example in the ‘save` context is defined but not executed.
   3. The ‘get’ context is evaluated, which causes the two examples there 
   to be defined but not executed.
   4. The ‘update’ context is evaluated, causing the earlier saved record 
   to be updated. Two examples are also defined but not executed.
   5. Now that RSpec has finished loading all the specs, it begins running 
   them. It starts with the example in the ‘save’ context, then the examples 
   in the ‘get’ context, then the examples in the ‘update’ context.
   6. Since the ‘update’ context had earlier updated the record, the 
   examples in the ‘get’ context fail.

To avoid these problems, here’s what I suggest:

   - Move your record manipulation logic out of the context blocks and into 
   a before hook defined in those contexts. This ensures that the logic 
   will run just before the examples execute that depend on them, regardless 
   of what order the examples execute in.
   - To ensure independence of your specs, you need each spec to work off 
   of a clean DB slate. The easiest way to achieve this is to wrap each 
   example in a DB transaction, so that any changes made in one example are 
   rolled back when it completes. The sequel docs 
   
<http://sequel.jeremyevans.net/rdoc/files/doc/testing_rdoc.html#label-RSpec+-3E-3D2.8>
 have 
   an RSpec code snippet that shows how to do this.
   - I recommend adding --order random to .rspec so that your specs run in 
   random order. This will surface ordering dependencies to you so that you 
   can quickly fix them at the time you create them, rather than having them 
   sit dormant in your test suite only to cause problems later. Each time 
   RSpec runs it will print the seed it used for that run’s randomization, and 
   you can use --seed <seed> to reproduce the run.

HTH,
Myron

-- 
You received this message because you are subscribed to the Google Groups 
"rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/rspec/e78053eb-2d29-450f-aa00-7222c8c0e9c7%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to