At Made By Munsters, we use the Global gem to handle hard-coded configuration duties. Global works by reading YAML files, and then converting them into a Struct-like, nested configuration. The nested configuration is then made available through dynamically built attributes under a root Global singleton.

For instance, if you have a config that looks like this:

yaml # config/global/mail.yml default: operations: name: Operations email: ops@example.com

With Global installed, you can then read the configuration at Global.mail.operations.email.

Recently, when I was trying to write a test verifying that new components were reading from Global configuration values, I ran into a problem. RSpec's mocking system–the project uses the term "doubles" for mocks–uses verifying doubles by default. That means that RSpec will raise an error if you attempt to mock a method on an instance or class that doesn't define that method. Verification helps ensure that the doubles you're referencing in your tests have similar APIs to the real implementations they stand in for.

While automatic verifying doubles are usually quite handy, there are times when they're not the right choice for your tests. One case is when you're developing exploratory tests against unwritten, amorphous collaborating components. Another is the case of working with dynamically composed components that don't play well with RSpec's verification mechanism. For class doubles like Global, RSpec will verify that a method exists by checking if the class responds true to respond_to?(:method_symbol). A false result will trigger RSpec to raise an error. Global doesn't return true for dynamically added properties, even if it does provide a value for the property. For instance:

pry > Global.mail => {"operations"=>{"email"=>"ops@example.com"}} pry > Global.respond_to? :mail => false

Trying to mock the Ops email address results in this:

pry > allow(Global).to receive_message_chain(:mail, :operations, :email) { 'test@localhost' } RSpec::Mocks::MockExpectationError: Global does not implement: mail

We can get around this limitation by tricking RSpec by having Global return true for any respond_to? query:

pry > allow(Global).to receive(:respond_to?).and_return(true) => #<RSpec::Mocks::VerifyingMessageExpectation Global.respond_to?(any arguments)>

With that in place, we can now successfully mock values returned by Global:

pry > allow(Global).to receive_message_chain(:mail, :operations, :email) { 'test@localhost' } => [#<RSpec::Mocks::Matchers::ExpectationCustomization:0x007fec81081838 @args=["test@localhost"], @block=nil, @method_name=:and_return>] pry > Global.mail.operations.email => test@localhost

There you have it: how to mock Global values for your RSpec tests, and how to similarly deceive RSpec's verification guards when mocking any other similarly dynamically constructed classes.

More posts
  • How Slack Helps Our Remote Team Communicate

    It’s a common misconception that remote teams can’t work as efficiently as in person teams, however thanks to Slack, Made By Munsters is able to create a workspace that allows for constant communication in our modern day work environment. Here is a little insight on how Slack works within our team and how to get the most out of the application.

    Read More
  • Build Your Product For Day One

    We work with companies of all sizes. But our team always looks at the immediate needs and problems our client faces and then defines a solution for day one.

    Read More
  • Ruby Remote Conf Recap

    This years conference had some great talks covering a gauntlet of topics, such as, static analysis tricks and SOLID design principles. It also featured several soft talks that covered surviving the framework hype cycle, and increasing inclusion / participation within teams, communities, and open source projects.

    Read More