Ruby RSpec: Mocks, Doubles, and Spies with Constructor Injection

Inversion of Control (IOC) containers or Dependency Injection is a common design pattern used to help engineers "inject" different instances of an object at runtime into services, controllers, or gateway classes. This concept is somewhat foreign to the Ruby on Rails community but valuable in some contexts. Most unit tests in RoR won't utilize mocks and stubs and instead opt for direct integration with the database. This article provides an alternative to integration testing by unit testing through constructor injection.


In this example, we will test the business logic that operates between an API request and an external, third-party web service.


I will note that this pattern forces developers to consider not using patterns like that in active record callbacks. The problem with callbacks is they are hard to test and can conflate what business rules exist in a domain context. Instead, you can move these operations into "service" classes. This enables future developers to better see the workflow and lifecycle of a domain model and its dependent resources. It forces you to be more intentional and ask what am I trying to solve and how can I communicate this through code enforcing patterns and succinct testing.

Song Model

This model represents a song object that can encode and decode to/from JSON.

Song Service

This class instruments the business logic and handles interactions with the external API using a "Gateway Service."

Song Gateway Service

This API (gateway) service enables us to proxy calls to the external API. This instruments the HTTP interfaces and allows us to mock these calls during testing.

Dependency Injection

If you notice in the song service, we "inject" the gateway service by passing it through the initializer. This is called "Constructor Injection."
We can then override the gateway class when testing the song service by injecting a mock or stubbed version. This allows us to avoid implementing the HTTP endpoints enabling a unit level of testing.

Testing with Mocks

When writing tests, we can inject a new implementation of the gateway service. One way to do this is to implement a new class that implements all methods within the scope of the test.

Testing with Doubles

RSpec provides a "double" interface that can stand in for any object in the test cycle.

Testing with Spies

RSpec also provides spies, a form of double where you can "spy" on the object interactions. This is my favorite of the three because you can verify how many times the doubled object method was called.

Conclusion

Rails is a great framework, and the ruby community could learn from other web frameworks when enabling and optimizing for better testing. Constructor injection is one tool that may be useful in your testing toolbelt and in improving the structure and readability of your code.


Comments

Popular posts from this blog

Atmosphere Websockets & Comet with Spring MVC

Microservices Tech Stack with Spring and Vert.X