September 2022

Avoid the pain of mocking modules with dependency injection

When you’re unit testing, there is no way around it: from time to time you’ll need mocking. Modern testing libraries have support for mocking modules. This means injecting mocks into the module system. Both Jest and Vitest have support for this.

When you look at the examples in the documentation, it seems simple enough. But when you try to do it in a production code base, it becomes a big frustrating mess:

It would either work, or not work at all. It just felt completely random at times.

I have no clue what is happening behind the scenes when I run my tests.

It just feels so extremely magical.

Those are quotes from a thread I came across. Luckily, it doesn’t have to be this way.

Mocking modules is a TDD anti-pattern

Before we get into dependency Injection (DI), let’s see how mocking modules causes so much pain.

Since we hook into the module system, tests that share modules can no longer run in isolation, they will affect each other: if a module is mocked in one test, it will have to be mocked in other tests. And if you’re not careful, mocks from one test will creep into other tests. This is a TDD anti-pattern:

you should always start a unit test from a known and pre-configured state

A mock from one test creeping into another test is not “a known and pre-configured state”.

Modern testing libraries like Jest and Vitest do a good job of avoiding these problems because they:

  • Keep the module systems of test files separated
  • Always run tests from one file in the order that they are defined
  • Suggest clearing all mocks after each test with an afterEach()

But you still have to be careful what you’re doing:

  • Want to mock a module in one test, but not in the other? Too bad, you’ll have to put the tests in different files.
  • Forgot to clear mocks in a test? Pain.
  • Using it.concurrent() to run tests in parallel? Pain!

And on top of that: you need to know the library-specific API’s to inject your mocks into the module system.

Maybe this doesn’t seem too bad, but things can get quite subtle: you run your tests locally, everything seems fine, so you push the code. Your CI pipeline runs the tests, everything green, great! Life is good.
The next day you push some completely unrelated code and your test from the day before suddenly fails, WTF is going on?!

This is what we call “flaky” tests. They’re pretty much bugs in your tests, the worst kinds of bugs: those you can’t reproduce predictably because they only happen sometimes. That’s what happens when your tests do not start from a “known and pre-configured state”.

Plain JS Dependency Injection

What if I told you all of this pain can be avoided by applying just one technique, no new syntax or tool, just plain JS. Introducing: Dependency Injection (DI).

Wikipedia describes the goal of DI like this:

dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs.

It says “constructing objects” because this technique has its origin in object-oriented programming. You don’t need objects (or classes for that matter) to make use of it, we’ll be constructing a function.

This is the function we will be testing:

And a Jest test that mocks the Axios module:

We have to mock the Axios module here because there is no other way to make getProducts() call a different Axios instance. The current implementation is tightly coupled to the default Axios instance.

We can fix that with DI, let’s see what the code looks like:

In the tests we can now use the factory function createGetProducts() to create our own version of getProducts with a mocked Axios instance. No need to get fancy, the mock is also plain JS:

By using DI instead of module mocking:

  • Your tests can run in isolation
  • You do not need to know any framework-specific syntax / module magic to inject a mock
  • Your code is more loosely coupled

Want to learn more? Sign up for the newsletter!

Stop trying to keep up with every new technology! Technologies come and go, the fundamentals of building good software don’t.

Learn the stuff every full stack developer needs to know, the stuff that will stay relevant for years to come!

No spam, no sharing your data with third parties, unsubscribe at any time.

Composing Software by Eric Elliot, what you need to know

Functional programming (FP) is a big deal in the Javascript community. It’s one of the fundamentals of building good software and great to learn if you want to grow as a developer.

FP has its roots in math academia, so a lot of the learning material is theoretical, making it hard to wrap your head around. It’s not always easy to see how you can apply it in your day job. The more practical information is scattered and often hidden in documentation for specific libraries.

I don’t know of any course or book that’s the reference on Functional Programming in Javascript. I have recommended Composing Software by Eric Elliott, but apparently some people don’t like that.

I do agree with some of the criticism, but it sounds like the whole book is rubbish and that’s not true at all. You can still learn a lot from it: most of the advice is good, it doesn’t go too heavy on the theory and the examples are practical.

There are a few things you should know: two OOP principles that come up in the book, object composition and the open-closed principle, are not used correctly. The book describes a forgotten history of OOP before getting into these things, but that’s not a good reason to confuse readers by redefining well-known OOP principles. Let’s see what’s going on here.

The wrong definition of Object composition

Skip the chapter on object composition. It explains that there are 3 types of object composition, but I’m not sure where these are coming from. It’s confusing.

“What is object composition” starts with a quote from Wikipedia, but it comes from the Composite data type page and not Object composition.

Then 3 types of object composition are described:

  • Aggregation: I don’t know where this definition of aggregation comes from, but in the classic GoF book and in UML, aggregation has a different meaning.
  • Concatenation comes back later in the book in the chapter about functional mixins. We will come back to this in the next section.
  • Delegation uses a form of object composition, so I can kind of see how it relates, but it’s closer to class inheritance than object composition. It’s not very relevant to what you should know about object composition and why you should favor it over class inheritance.

Mixins are not “object composition” in “favor object composition over class inheritance”

In the chapter “Functional Mixins” the confusion about object composition continues:

Mixins are a form of object composition

And then a few paragraphs further:

it is the most common form of inheritance in JavaScript

So… are mixins composition or inheritance?

Technically speaking, describing mixins as “object composition” is not wrong if you only consider the meaning of the words in English: you’re composing objects together to form a new object. But when we talk about object composition in OOP, this is not what we mean. Functional mixins are a form of multiple inheritance, not object composition.

It’s not a bad technique, it’s useful to know it and use it. Just remember that you’re doing multiple inheritance. The book seems to suggest that you’re applying “favor object composition over class inheritance”, but this is not true. In fact, the “Caveats” section has this warning:

Like class inheritance, functional mixins can cause problems of their own. In fact, it’s possible to faithfully reproduce all of the features and problems of class inheritance using functional mixins.

Composition is not necessarily harder with classes

In “Why composition is harder with classes” the book tries to make the point that composition is harder with classes because mixins are harder with classes. Since mixins are not object composition, this is not correct.

What you should remember from this chapter is:

  • A factory function is more flexible than new or class.
  • Changing from new or class to a factory function could be a breaking change.

A clumsy reference to the open-closed principle

Further in this chapter, there is a section called “Code that Requires new Violates the Open/Closed Principle”. It starts like this:

Our APIs should be open to extension, but closed to breaking changes.

This is good advice and the open-closed principle has a similar high-level goal, but it’s not what the open-closed principle means.

The open-closed principle generally means what Wikipedia calls the polymorphic open–closed principle. We would use interfaces rather than abstract base classes these days, but Robert C. Martin’s article is in C++ and from 1996. The concept of interfaces didn’t exist in the language, abstract base classes were the closest thing.

That’s not acceptable, aren’t there any other resources to learn from?

Specifically for Javascript there are some alternatives:

Personally I learned functional programming from an online course on Coursera: Functional Programming Principles in Scala. It’s a lot of work to learn a new language and translate what you learned to Javascript just to get into functional programming. I didn’t learn Scala just to get into functional programming. But if you’re serious, and you’re up for a challenge, feel free to learn Scala. Or go find the definitive learning material for purely functional languages like Haskell or Lisp. It’s not the most efficient way, but you will learn a lot, I promise!


Want to learn more? Sign up for the newsletter!

Stop trying to keep up with every new technology! Technologies come and go, the fundamentals of building good software don’t.

Learn the stuff every full stack developer needs to know, the stuff that will stay relevant for years to come!

No spam, no sharing your data with third parties, unsubscribe at any time.