I received an interesting question (two, in fact) about the nature of unmanaged dependencies and their relation to mocking, and I’d like to share my answers with you.
We’ll discuss the first question today and we’ll talk about the second one next week.
1. Managed vs unmanaged dependencies
Here’s the gist of it.
All out-of-process dependencies fall into two categories:
Managed dependencies (out-of-process dependencies you have full control over) — These dependencies are only accessible through your application; interactions with them aren’t visible to the external world. A typical example is a database. External systems normally don’t access your database directly; they do that through the API your application provides.
Unmanaged dependencies (out-of-process dependencies you don’t have full control over) — Interactions with such dependencies are observable externally. Examples include an SMTP server and a message bus: both produce side effects visible to other applications.
Communications with managed dependencies are implementation details. Conversely, communications with unmanaged dependencies are part of your system’s observable behavior:
This distinction leads to the difference in treatment of out-of-process dependencies in integration tests:
Use real instances of managed dependencies; replace unmanaged dependencies with mocks.
The reason for such a difference is the requirement to maintain backward compatibility with managed and unmanaged dependencies.
You must maintain backward compatibility with unmanaged dependencies. Mocks are perfect for this task. With mocks, you can ensure communication pattern permanence in light of any possible refactorings.
However, there’s no need to maintain backward compatibility in communications with managed dependencies, because your application is the only one that talks to them.
External clients don’t care how you organize your database; the only thing that matters is the final state of your system. Using real instances of managed dependencies in integration tests helps you verify that final state from the external client’s point of view. It also helps during database refactorings, such as renaming a column or even migrating from one database to another.
2. What exactly is an unmanaged dependency?
So here’s the question about unmanaged dependencies:
I don’t understand term full control.
Is it full control if my app is the only writer/reader for a database, but cannot create tables in it? What if the DB is managed by the DevOps team in my company?
Or, I have a DB in the Cloud, which I can access via the API as a client. Only my app is writing to it, but the DB itself controlled by the Cloud. Is it unmanaged?
Another example, I have a service for sending emails. It uses Mailchimp as an implementation detail. Is Mailchimp also an unmanaged dependency, because other companies also use it’s API?
I see why the definition of managed and unmanaged dependencies is confusing — I shouldn’t have used the term "full control" when describing them.
The main and the single differentiating factor between managed and unmanaged dependencies is whether the state of the out-of-process dependency (or changes to said state) is observable externally.
In other words, whether other applications can read that state only via your application or whether they have other means to observe it.
So, let’s take the two example from the question above and look at them using the refined definition.
The database hosted in the Cloud — It is still a managed dependency, even though your application technically doesn’t have a full control over it. The reason is that no other application has access to it.
The email service that uses Mailchimp at the backend — It is an unmanaged dependency because changes in it (sending of an email) are visible to other clients directly: those clients have an email app that can download the email message without the help of your application.
Here’s how you can visualize the difference between managed and unmanaged dependencies (click to enlarge):
If an out-of-process dependency only connects to your application (like the Cloud database), then only your system can observe its state, which means it’s a managed dependency.
The Cloud itself doesn’t count here for the same reason the OS (Linux or Windows) doesn’t count — it’s not a client of your database.
Conversely, if the out-of-process dependency connects to other applications (like the message bus), then those other applications can also observe that dependency’s state (or rather, changes in that state). Hence, the dependency is unmanaged.
Hope it clarifies the difference between managed and unmanaged dependencies.
Next week, we’ll discuss whether you should always mock unmanaged dependencies.
Enjoy this message? Here are more things you might like:
Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email to discuss.
Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »
My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »