I’ll introduce a new pattern for testing, against external APIs (like HTTP#get ) with ruby 2.1 refinements instead of Dependency Injection.

“Interceptor Injection pattern” is a word coined by me. Better idea for this pattern is always welcome.

code

Our application or libraries call many external APIs, which is not under control by us. These may be an instance method or a singleton method.

# testing_file.rb module Testing def self.foo content = AnHTTPClient.new.get "http://mygreatservice.example.com/person/1.json" JSON.parse(content) end end

This code works fine if mygreatservice.example.com respond to your requests. Of course, we cannot expect such great situations in testing.

tests with DI patterns

DI patterns works fine for many cases.

# testing_file.rb require 'activeresource' class Person < ActiveResource::Base self.site = "http://mygreatservice.example.com" end module Testing def self.foo content = Person.find(1) JSON.parse(content) end end

This code works fine when called normal context. For testing, we can replace Person.site with dummy site or also can replace background API call of Person ’s http request to return dummy data defined by fixtures.

This is suitable when your code is for web applications, or standalone application itself.

But for plugins or libraries, it doesn’t works well. Plugin code testing environment MAY NOT provide good frameworks to replace external APIs with dummy modules.

tests with Interceptor Injection patterns

In Interceptor Injection pattern, testing code replace external module APIs with mocks directly by refinements, new feature of Ruby 2.0 (and modified in Ruby 2.1).

For refinements itself, see document in ruby-lang.org. (In short, Refinements rewrites behaviors which appears in file with using .)

# testing_file.rb module TestingInterceptor; end using TestingInterceptor module Testing def self.foo content = AnHTTPClient.new.get "http://mygreatservice.example.com/person/1.json" JSON.parse(content) end end

This code is same with the first example, without curious TestingInterceptor blank module definition, and using . This code works how we wants. Curious two lines do nothing in development/production environments.

In testing, we want to replace AnHTTPClient#get with dummy method that returns dummy json data directly. Test code should define refinements.

# testing_file_spec.rb module TestingInterceptor refine AnHTTPClient do def get '{"id":1,"name":"matz"}' end end end require "testing_file.rb" # this MUST follow refinements describe Testing do describe ".foo" do it "request JSON content, and returns parsed data" expect(Testing.foo).to eql({id:1, name:"matz"}) end end end

Test code with Interceptor Injection pattern have some pros:

it can overwrite any methods/singleton-methods which accessed in file now testing it does NOT overwrite any definitions which accessed in outside of testing file it does not need supports of testing frameworks

Refinements does not have any effects outside of file with using TestingInterceptor . It does not break any behaviors outside of this file.

DI frameworks replaces all of instances which defined to be replaced in configurations. We should to consider what are replaced and what are not.

By this reason, we can overwrite any methods of any modules for testing. It is very simple and powerful for testing.

For class methods

If you want to overwrite AnHTTPClient.get (instead of AnHTTPClient#get ), you can do it with this refinements code.

module Interceptor refine AnHTTPClient.singleton_class do def get '{"id":1,"name":"matz"}' end end end

This code is messy and hacky, but works.

cf. https://gist.github.com/tagomoris/9183222

Conclusion

I introduced that Refinements is a powerful feature for testing. I named it as “Interceptor-Injection pattern”. I want what you think about this idea!