Today I figured out testing Grape Helpers


How to access Grape API helpers for Unit or other testing

At work, we use Grape for most of our Rails APIs. At 2.1m downloads and counting Grape is widely used and one assumes widely tested.

However, Grape helpers are difficult to call for testing because they live as an anonymous Module in memory.

So how do we call grape helpers in order to test?

First, we need to find all the Module(s) that are grape helpers.

mods = ObjectSpace.each_object(Module).to_a.select{|obj| obj.is_a?(Grape::DSL::Helpers::BaseHelper)}

This returns an array of all the helper modules so we will filter by querying for the method we want (:foo) using +method_defined?+

mods.select!{|mod|mod.method_defined?(:foo)}

Here we may still have an array with more than one member. At this point though they all include the Grape BaseHelper module so we can call +api+ on the modules. Especially helpful if there are different versions of the same named helper in different versions of your API

mods.first.api

In my case I had just two modules that both returned “API::V1::Bar” so I just picked the first one and that worked out fine. I tried it with the other and got the same result so whatever.

Here’s where it gets tricky. Grape helpers exist as UnboundMethods, which I just learned existed today. First we need the UnboundMethod instance.

unbound_foo = mods.first.instance_method(:foo)

In order to call the method it needs to be bound. In the laziest possible implementation, that looks like this:

c = Class.new
helper_foo = unbound_foo.bind(c)

Now we are in business. All that remains is to call the method.

helper_foo.call

However, you may have helpers that call into other helpers, making the .call syntax less than helpful. I ended up doing this:

mod.instance_methods.each do |name|
mod.send(:module_function, name)
mod.send(:public, name)
end

And then it’s as simple as

mod.foo

Looking back this all makes some sort of sense. Just like on a class, you can’t call instance_methods until the class has been instantiated. The modules that Grape creates are used as Mixins for your API classes and true to form the helper methods can’t be called without a class instance.

Still I’m surprised at how difficult this code was to find and test. We should not need to iterate through ObjectSpace just to test code that we wrote ourselves.

Show your support

Clapping shows how much you appreciated Paul Sernatinger’s story.