Search code examples
rspecrspec-rails

Can Rspec be relied upon to rollback transactions?


Earlier today I began running into an odd RSpec issue where about 5% of my tests just stopped passing. The major common denominator was that most of the failures were related to situations where the test was counting the total number of instances within a class (e.g. expect(Object.count)). The type of error would be something like this:

expected: 2 got: 10

I wanted to drill in further on the specifics of a single test to see what was happening. I discovered that every time I ran the test, the number of Instances of a specific object would increment by one (even when I commented out every single part of the test). It was like the test database was instantiating an object every single time the test was run for no apparent reason. I checked my rspec config to ensure transactional fixtures was set to true. I've looked for an answer to the question of why the test database data didn't seem to be clearing out, and it seems like the general consensus was "just use database cleaner". That being said, I was able to get the test to finally pass by inserting

DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean

right before my test, but this seems like a clunky solution as some people say that it slows down the testing suite. I'm somewhat hesitant to create a bunch of before actions in my test suite that incorporate Database Cleaner if this isn't the root of the issue. I've run into variations of this problem before (e.g. the test db not clearing out). Is the general thought simply that Rspec can't be consistently relied upon to rollback transactions after each test execution?


Solution

  • It turns out the issue in this situation was related to an Rspec shared example within the spec. Apparently, in a shared example, the code inside the it_behaves_like block is not transactionally rolled back like the other tests within a spec.

    Here's a simplified example of the code that was causing the number of instances of Object to increase every time the spec was run:

      it_behaves_like "require_sign_in" do
        object = Fabricate(:object)
        let(:action) { delete :destroy, id:object.object_id }
      end