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.