Test Architecture of a Rails App

29 Apr 02:20

The abundance of testing Gems and the great support for testing in the Rails framework make it really easy to write tests. But often enough, its not quite obvious to a developer where to put the tests and how to test. Here I will give you some guidelines how and where to test what and why you should test it that way!

Lets take a look at the components that are usually involved in a user request and how they are usually tested:

  1. Routes - Untested: Implicit testing trough acceptance tests
  2. Controller - Integration: Controllers are tightly coupled to Rails (ActionController::Base) and often horizontal Libraries like Devise. Stubbing out Rails and horizontal Libraries is complex and makes your application most likely less maintainable.
  3. Model - Integration: Against the Database. Isolation makes no sense most of the time.
  4. Application Logic - Isolation
  5. Service Facade - Integration
  6. Decorator/Presenter - Isolation
  7. View - Isolation

A typical, minimal user request has to at least involve the routes, controller and view step but most requests will go trough all 7 layers. Two of this layers need a little bit of explanation because they are not part of many Rails applications as of today, but absolutely should: Application Logic and Service Facades.

Service Facade

Service Facades are thin layers between your Application and a 3rd Party Gem. The advantage in using them lies in the possibility to change dependencies quickly. An example: If you are using the Facebook API you probably rely on a external Gem to provide that functionality. The Service Facade is a wrapper above this Gem that brings a little bit of app-specific logic to the Gem and gives you a layer of abstraction if you want to change out the Gem.

So how would you test a Service Facade for Facebook? You could stubb out the Gem, but given the rapid development in Gems and the possibility that you want to exchange this Gem for another thats not a good candidate. You could also simulate the Facebook API and write your own little Facebook Server stub, but that can get complex very quickly.

The best approach is to stub out the network. This is extremely easy with tools like vcr-gem. You simply record the requests to the Facebook API once and replay them each time you run your Service Facade integration tests.

Application Logic

Most books, tutorials and other resources for Rails are centered around application logic living in the Model, Controller or even View. This approach has some drawbacks, especially when you are designing for testability. The Application Logic is the core of your application and you want to test it easily, have all the functionality in one place and you should test it in isolation for speed and reliability reasons.

In a modern Rails Application with good architecture you will find a Application Logic component which relies on exactly two other components: Model and Service Facades. The big advantage of this structure is, that you have full control over the boundaries of your application logic. This is a huge improvement over a architecture where your business logic lives in the model layer. In such an application you have to integration test your logic because you rely on a external service which you don't control, the database. The same applies to the Service Facades: without them you have a external boundary, the Gem/API.

Dependencies Overview

 Route        
 ⇒  Controller      
  Model     
   App Logic    
     ⇒  Model  
     ⇒  Service Facade  
   ⇒  View    
     ⇒  Presenter  
       ⇒ Model