

Testing external dependencies using dependency injection
source link: https://sourcediving.com/testing-external-dependencies-using-dependency-injection-ad06496d8cb6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Testing external dependencies using dependency injection


If you work on a bigger Ruby (on Rails) project, chances are that you need to interact with some external resource like a database, cloud API or message queue. Testing these external resources can sometimes be tricky, let’s have a look at different approaches.
Example
As an example, we will use the Dropbox API gem to upload a file.
We have an Uploader class which accepts the file content and file name. Finally it uses the Dropbox client to upload the data to the cloud.
WebMock
An approach we often see is to use WebMock to stub the HTTP requests.
From the official documentation, it is described as a
Library for stubbing and setting expectations on HTTP requests in Ruby.
Our test code could look like this
With this approach, we test that the correct Dropbox API URLs are called. This has the advantage that we can refactor our code and even use a different library and as long as we still call the same URLs this test would pass.
However, if Dropbox decides to update their API we would need to change our specs. If we’re treating the gem as our public interface, and trusting it to be maintained and respond to API changes, then having to update those specs every time seems like an unnecessary step. We could argue our test might have too much low-level details and this should get tested in the gem instead.
PORO fake
An alternative could be to build our own Plain Old Ruby Object (PORO) fake.
In case we want to swap out Dropbox for another cloud storage, we would need to change the Uploader class (open-closed principle). Instead, Uploader should accept a client parameter which is called dependency injection.
As we now only need to provide an object which responds to upload_by_chunks we can implement a simple test client fake too.
The cattr_accessor is a handy helper to create a getter and setter for a class attribute. In our upload_by_chunks method, we just store the parameters in this class attribute. We can now use this test client in our specs like this.
However, our Uploader class is now tied to the public API of the Dropbox gem. If the upload_by_chunks method is renamed for example, the class would have to be modified even though from its perspective nothing has changed.
A way to avoiding this is sometimes referred to as the “dependency inversion principle”, that suggests to “depend upon abstractions — not concretions”. We can fix this by wrapping our DropboxApi::Client into a DropboxClient adapter with a common ‘interface’ upload.
With this change we also make the public interface of the uploader client smaller (interface segregation) and easier to support more clients (e.g. Box) in the future as we only need to write a new adapter.
We don’t couple our specs to the Dropbox API or gem anymore as we test against our adapter interface. As always, there is however a trade-off: We need to maintain our PORO fake and keep it in sync with our adapter and specs might still pass even though the Dropbox gem or API changed. We should test our wrapper class too.
Summary
By using dependency injection with a PORO fake, our specs and implementation not only became more extensible but also more robust against changes. It might not be needed for every situation, but used at the right moment can be a useful tool.
Some examples where we use this pattern at Cookpad include sending SMS messages, Kafka messages, push notifications and even Redis.
Recommend
-
39
What is a dependency? A dependency is a piece of code (either a library, class, object or any other data type) required by another piece of code to work. Put simply, if module A re...
-
9
FsAdvent 2020 - Dependency Injection Using Flexible Types and Type Inference 12/5/2020 Dependency Injection Using Flexible Types and Type Inference...
-
20
F# Dependency Injection - how to compose dependencies with partial application and don't fail with testsOne question you might ask yourself before starting a bigger project in F# How to inject dependencies? Let me show you how we use...
-
13
Dependency Injection in C++ Using Variadic Templates A few days ago I was reading up on variadic templates and it occurred to me that they could be used to automate dependency injection. So I started playing with some code...
-
18
3 hours ago2021-09-13T00:00:00+02:00 by Wolfgang Ofner In my last post, I created a .NET 5 cons...
-
6
Dependency Injection in JavaScript — the Best Tool You’re Not Using for your TestsLet me introduce you to your new testing best friendPhoto by
-
10
Assembly Encapsulation with Dependency Injection – Unit Testing After his great post on Assembly Encapsulation with Dependency Injection, Andrew Hinkle follows up with unit testing the assembly encapsulation Written by An...
-
9
Lightweight dependency injection and unit testing using async functionsVery often, making code easy to unit test tends to go hand-in-hand with improving that code’s separation of concerns, its state management, and its overall archit...
-
10
Introduction When it comes to maintaining a large application codebase, sometimes you are confused with dependencies. Moreover, when you create a new struct that depends on a struct with many attributes, you need to provide t...
-
13
Sinatra is often seen as a tool for simple APIs, but it can also be used to manage big applications. dry-rb libraries can help you create a modular architecture with system wide dependency injection for your application. Table of co...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK