One of the things that I dislike about Rails is how some actions feel untestable. Take, for example, the act of grabbing the current user.

This is bog-standard code that you’d see in a lot of Rails tutorials and people’s codebases. What I don’t like about it is that the current_user method is not easily testable. Heck, which test file do you put the tests in?

Another example is the login/logout functionality that exists in the same boilerplate way.

Fairly common eh? And yet, should the business logic of handling login and logout functionality be handled (and tested) in the controller? No, and this is a code smell.

At this point I like to create a service object that handles some common functionality found in all the above controllers, but with a nod to being easily testable and self-contained from a controller scope. Another added benefit is that the service object is typically devoid of any Rails framework calls so testing can happen quickly and without side effects because each dependency is injected into the object at runtime.

That’s a lot of code, but each method is deceptively simple. I’m using contructor injection to specify the session, the session key and the object that knows how to find user records. Sensible defaults are given so that callers don’t need to specify everything (this is one thing that Ruby does well versus, say, Java). The session and session_key are used to look up the user ID in the session.

I keep all service objects in a directory called app/services so that (a) they are loaded automatically by Rails and (b) they are in a common place within the codebase. This is in contrast to putting code in lib, which I find to cause search nightmares. The corresponding tests for each service object are held in test/services.

To test the service object you will first need to tell Rake to use the test/services directory. Put the following code in a file in the lib/tasks directory and Rake will pick it up (give the file a .rake extension too).

With that file in place, you can now do rake or rake test:services and your service objects will be tested.

So how does one test a service object? It’s quite easy since the majority of the dependencies are injected. This allows you to use tools like mocha to mock out the method calls that you don’t care about, freeing you from having to tie yourself into the Rails framework. Here is what the test file looks like.

Putting this service object to use allows us to simplify the current_user, login and logout functionality displayed previously. It also allows me to be more confident that each code branch will be tested properly.

One last feature of using a service object such as SessionService is that the handling of the session object for holding user IDs (denoting whether the user is logged in or out) is kept in one place. If you should ever change session[:user_id] to session[:admin_id] without this service object, you’d need to search through your codebase to find all instances, whereas with a service object it is all held in one place.