rspec-mocks: 'not defined constant' errors when using instance_double

I have the following:

let(:project) { instance_double 'Project' }

But when I run specs that access this double, I get Project is not a defined constant. Perhaps you misspelt it?

I have the following in my spec_helper.rb file:

config.mock_with :rspec do |mocks|
  mocks.verify_doubled_constant_names = true
end

I have a Project class with:

class Project < ActiveRecord::Base

What am I missing here?

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Comments: 28 (18 by maintainers)

Most upvoted comments

At the time your spec runs, Project is not a defined constant. Are you loading it anywhere (e.g. with require 'project' or similar)?

If you are relying upon rails autoloading, you can change instance_double 'Project' to instance_double Project (it works fine to pass the class itself rather than the string) – referencing the constant will cause rails to load the model.

In general, if you always want your verifying doubles to verify and are willing to pay the cost of loading the classes, passing the class as a constant reference works simpler/better than using the string form and verify_doubled_constant_names = true. OTOH, using the string form provides a way to write an isolated spec that doesn’t require the named class to be loaded but will verify when it is loaded. When doing that, I tend to set verify_doubled_constant_names = true in a config file that is only loaded when all specs are run (and all implementation files are loaded). For individual spec files, using verify_doubled_constant_names = true kinda destroys the whole point of passing the class name as a string, as it prevents you from being able to run the spec w/o the class loaded, which is the entire point of supporting the string form in the first place.

Closing, but let me know if that doesn’t make sense and we can reopen.

In an imaginary ideal world, what I want is for these features and their benefits to just all work as advertised in a Rails app. And a pony.

That would mean:

  • I can use verifying doubles with strings, not constants, so I get the fabled “best of both worlds” behaviour: when the class hasn’t been loaded I get a vanilla double, and when it has been loaded I get verification. This means I can run individual unit tests (spec_helper.rb) quickly in isolation without having to load the whole app, but when I run all the integration/acceptance tests as well (rails_helper.rb) I get failures if I’ve accidentally stubbed methods that don’t exist. There is a smooth continuum here: if I run a subset of unit tests such that some tests happen to load classes that other tests reference with verifying doubles, I get a bit of checking.
  • I can use verify_doubled_constant_names. This means I see failures if I accidentally make a typo in the strings I use to create verifying doubles, whenever that is possible to detect.

I understand the tension here, since it’s hard to tell the difference between “this class doesn’t exist” and “this class hasn’t been autoloaded yet” in a Rails app. But at a minimum it would be helpful to have some documentation that says: we recommend you do such-and-such if you’re using Rails. (There are a few candidates in this thread already, but those are just GitHub comments, not official advice.) If that recommendation is to have a special separate Rake task for preloading everything and running with verify_doubled_constant_names because autoloading and name verification don’t mix, or even to not use name verification at all with Rails, that’s absolutely fine. Just say so.

A more ideal solution in principle (although perhaps impossible in practice) would be for name verification to decide whether a particular constant name looks plausible for the current Rails app, i.e. does there exist a file whose name and path would cause Rails’ autoloader to try to define this constant if we referenced it? That would preserve the benefits of “we don’t have to load everything” but without the downside of “if we make a typo in a name string, we may literally never be told about it”.

In any event, the eventual solution must account for the realities of Rails. Rails projects are not like plain Ruby projects. Rails applications cannot have all of their dependencies loaded with require 'project'. Most Rails devs don’t even know how autoloading works, so they need specific documentation (if not automatic implementation) of “just require all your files”; Rails does not do this for you in the test environment.