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.
