Here's a topic that pops up often especially when faced with the common task of testing controllers in Rails. We have some logic inside a private method and we want to test it somehow. And of course, like most things in ruby, there are a few ways to do it which we’ll look at next.
Testing private methods directly
This is the most popular one mostly because it’s quick and it’s also the first one you’ll find by google-ing (or searching on stackoverflow.com). It’s all about using the send method to call the private method directly. So you call it like any other public method and voilà, your test is done.
Let’s see how this looks like in code:
class MyClass
def self.my_public_method
my_private_method
end
private
def self.my_private_method
"private method"
end
end
So here’s a RSpec test for it:
it "tests the private method directly" do
expect(MyClass.send(:my_private_method)).to eq("private method")
end
There are mixed opinions about testing private methods like this so I’m gonna lay out the pros and cons. But I have to mention that it’s not my favourite.
Pros:
- It’s quick and easy to write the tests
- You can test it once then mock it later
Cons:
- It breaks encapsulation
- It makes tests harder to refactor (since you’re testing an implementation detail)
- Goes against Behaviour Driven Development (BDD)
Testing private methods through their interface
Another way of testing private methods is through their public interface. This means that you have a public method that makes use of the private one and you’re going to test the private method through it.
So given the same ruby class defined above, here’s how our rspec test looks like when testing it’s public method’s behaviour:
it "tests the private method through it‘s interface" do
expect(MyClass.my_public_method)).to eq("private method")
end
The nice thing about this method is it respects encapsulation but on the other hand if you’re unlucky enough to work with existing code that’s not really well structured, it’s a bit more work to organize it properly (it could take a fair amount of time, depending on it’s complexity of course).
Pros:
- It forces you to design your code better
- It respects BDD (testing the behavior and not the implementation)
- It makes code and tests easier to manage
Cons:
- It’s not easy to use on spaghetti code so you might need to spend some time refactoring
Extracting the code out into a different class
Another way to organize your code is to extract the logic from the private method into a separate class (or more) that is specific to one problem. By extracting the logic into a separate class (even if it’s a nested class), it makes it easier to test and read. It’s a good practice to separate logic into smaller objects if you can afford the time.
Let’s see an example:
# /lib/my_class.rb
class MyClass
def self.my_public_method
MyOtherClass.my_second_public_method
end
end
# /lib/my_other_class.rb
class MyOtherClass
def self.my_second_public_method
"second method"
end
end
# /spec/lib/my_class_spec.rb
RSpec.describe MyClass do
describe ".my_public_method" do
it "calls MyOtherClass's .my_second_public_method" do
expect(MyOtherClass).to receive(:my_second_public_method)
MyClass.my_public_method
end
end
end
# /spec/lib/my_other_class_spec.rb
RSpec.describe MyOtherClass do
describe ".my_second_public_method" do
it "returns 'second method'" do
expect(MyOtherClass.my_second_public_method).to eq("second method")
end
end
end
Pros:
- It’s better OOP to organize code into smaller and more focused objects
- Makes for easier testing
- Test once, mock later
Cons:
- It’s more work to extract everything out
- Some people don’t like having to search through tons of files
Private methods and controllers
We tend to use the private methods a bit more in Rails so I think it’s worth sharing an example of how you would test private methods inside of a Rails controller.
It’s nice to have a finder method for each action defined as a private method so you code is a bit more DRY, but then there’s the question of testing it.
Some prefer to use the direct method and get away with it but for those of us who don’t like using the direct method, there’s another way.
We can test one action’s behavior which will also test the private method indirectly but then what do we do with those other actions that make use of the private method? Do we write the same tests for each one? Of course not.
We can use a shared example that will test the private method’s behaviour thru each of the actions that are using it.
Using a shared example becomes useful when you want to test that a public method “includes” some behaviour that’s common across multiple methods.
Let’s see an example of a controller who’s actions need a finder.
class MyController < ApplicationController
before_each :find_my_user, :only => [:show, :edit]
def show
end
def edit
end
private
def find_my_user
@user = User.find_by!(:id => params.require(:id))
end
end
Testing .find_my_user
’s behaviour through both these actions is easy enough using a shared example:
require 'rails_helper'
RSpec.shared_examples "a finder" do |action_name|
it "returns a string" do
allow(User).to receive(:find_by!).with(:id => "1").and_return(user = double)
get action_name, :id => 1
expect(assigns(:user)).to eq(user)
end
end
RSpec.describe MyController, :type => :controller do
describe "GET show" do
it_should_behave_like "a finder", :show
end
describe "GET edit" do
it_should_behave_like "a finder", :edit
end
end
Final words
If you need (or like) to start with everything inside a the public method, I’d say make it pass first, even if it’s all in one huge method then extract bits of logic out into private methods or different objects while keeping your tests green.
I would love to hear how you’re solving this issue or if you have a better way that I’ve missed.
If you want to read some more about the subject of testing private methods here are two good articles I’ve found:
- Ruby Access Control – Are Private And Protected Methods Only A Guideline? by Alan Skorkin
- Testing Private Methods by Avdi Grimm