I have a module like this that I'm trying to write unit tests for
module MyThing
module Helpers
def self.generate_archive
# ...
::Configuration.export(arg)
rescue ::Configuration::Error => error
raise error
end
end
end
The ::Configuration module can't exist in my unit testing environment for reasons that are beyond my control, so I need to stub it out. Here's what I've come up with so far.
RSpec.describe 'MyThing' do
it 'generates an archive' do
configuration_stub = stub_const("::Configuration", Module.new)
configuration_error_stub = stub_const("::Configuration::Error", Class.new)
expect_any_instance_of(configuration_stub).to receive(:export).with("arg")
MyThing::Helpers.generate_archive
end
end
This gets me an error.
NoMethodError:
Undefined method `export' for Configuration:Module
If I put the configuration_stub definition inline with the expect_any_instance_of like this
RSpec.describe 'MyThing' do
it 'generates an archive' do
configuration_error_stub = stub_const("::Configuration::Error", Class.new)
expect_any_instance_of(stub_const("::Configuration", Module.new)).to receive(:export).with("arg")
MyThing::Helpers.generate_archive
end
end
I also get an error.
NameError:
Uninitialized constant Configuration::Error
...
# --- Caused by: ---
# NoMethodError:
# Undefined method `export' for Configuration:Module
expect_any_instance_ofworks on instance methods.exportis being called as a class method.Instead, use a normal expect on the class.
Note: it is not necessary to write
::Configurationin the tests. The::is to clarify betweenMyThing::Helpers::ConfigurationandConfiguration.Note: if you're calling methods directly on Configuration it should probably be a Class not a Module.
Note: instead of calling methods on a class, consider using a Configuration object.
App.config.exportwhereApp.configreturns the default Configuration object. This is more flexible.The problem with that is RSpec will verify that
Configuration.exportexists. It doesn't. You could turn off verification, or you could make a real class to test with.You could write a stub Configuration module for testing only and put it into spec/support, but your tests are increasingly divorced from reality.
The real problem is your project should have a real Configuration class!
I'm going to guess the real Configuration contains production information which cannot be checked into the repository. This is a common anti-pattern. The Configuration module should hide the details of where the configuration values are coming from. There should be independent development, test, and production configurations. There are many ways of doing this, the most common are to use environment specific config files, or to store environment specific config values in environment variables.
While you could mock the
Configuration::Errorexception, there should be no reasonConfiguration::Errorcannot exist in your test environment. Add it and simulate an error like so: