Let’s say you have such a class: (this code is borrowed from this Reddit thread
class CreateSomethingService def initialize(params) parse_parameters params end def run Something.create(name: @name) end private def parse_parameters(params) @name = params[:name] end end
How can we test this class without loading Rails?
One way to unit test it is by using the repository object and the concept of replacing the repo object with an in-memory one in tests.
class CreateSomethingService def initialize(repo, params) @repo = repo parse_parameters params end def run repo.create_something(@name) end private def parse_parameters(params) @name = params[:name] end end class SomethingsRepo def create_something(name) Something.create(name: @name) end end class InMemorySomethingsRepo attr_accessor :somethings def initialize @somethings =  end def create_something(name) @somethings << name end end class SomethingsTest def test_creates_somethings repo = InMemorySomethingsRepo.new CreateSomethingService.new(repo, "Arkency") assert_equal(1, repo.somethings.length) end end
Note that the service now takes the repo as the argument. It means the controller needs to pass the right repo in the production code and we use the InMemory one in tests. Obviously, if your implementations of the repos diverge, you have a problem :) (which best to mitigate by having integration tests which do run this code with Rails)
You can read more about the setup here:
It’s worth noting here, that it may be better to treat a bigger thing as a unit than a single service object. For example you may want to consider testing CreateSomethingService together with GetAllSomethings, which makes the code even simpler, as the InMemory implementation doesn’t need to have the :somethings attribute.
Services - what they are and why we need them This setup has its limitations (the risk of diverging), but it’s solvable. The benefit here is that you don’t rely on Rails in tests, which makes them faster.
If you like this kind of approaches to Rails apps, then you will enjoy more such techniques in my book about Refactoring Rails