How to Fix Circular Dependency Errors in Rails Integration Tests

Cameron Cundiff

We recently joined an existing Rails 4.1 project that uses AngularJS. We observed sporadic Capybara::ElementNotFound failures in RSpec feature specs with JavaScript. These failures were false positives and undermined our confidence in the test suite, preventing us from using a fluid outside-in testing process.

Finding the Problem

We saw in Capybara screenshots that the pages (rendered with AngularJS) were failing to fully load, but only in tests. In order to learn more, we reviewed test.log, and saw this error:

RuntimeError (Circular dependency detected while autoloading constant MyClass)

This looked like a race condition while loading files. Lazily loading files is not threadsafe in Ruby, and since Webrick is multithreaded and Rails.configuration.eager_loading is disabled by default in test, we suspected that concurrency was enabled.

In production environments, Rails 4.1 uses Rack::Lock if classes won’t be cached (true in development by default, but false in production by default). Rack::Lock creates a mutex around requests that guards against threadsafety issues in multithreaded environments such as Webrick (the test server). We expected that Rack::Lock would be used in tests as well, but to make sure we ran RAILS_ENV=test rake middleware, and saw that Rack::Lock was missing.

The Crux

The reason we were seeing the circular dependency error was that Capybara was making many concurrent requests to a multithreaded server (Webrick) with Rack::Lock disabled. This became an especially insidious problem in an AngularJS app, given the number of concurrent requests being triggered via AJAX.

The Solution

We concluded that we needed to force Rack::Lock middleware to be added in test. This was echoed in this Rails issue raised on GitHub.

Rails adds Rack::Lock unless allow_concurrency is true. It follows that we can explicitly set allow_concurrency to false in our test environment to force Rack::Lock to be added.

# config/test.rb
config.allow_concurrency = false

The issue will be fixed in Rails 4.2, but in the interim, explicitly disabling concurrency has fixed the circular dependency error we were seeing.

Practicing Red/Green/Refactor and outside-in testing depends on getting reliable failures from the tests, and we didn’t have that. Resolving flakiness in the test suite thus made a notable improvement in our TDD workflow while reviving confidence in the tests.