Import Mocking with Typescript

Emma McMillan
Sep 8, 2018 · 4 min read

Solving the issues with mocking dependencies inside Typescript.

“programming language illustration” by Christopher Robin Ebbinghaus on Unsplash

The Problem: Typescript tests

We were working on a NodeJS + Typescript project. All of our files contained classes. These classes used import {} from ''; syntax to import dependencies.

The dependencies broke our tests.

There are lots of easy ways to mock out dependencies using Javascript, but we were using Typescript. There are many easy ways of mocking Typescript objects, but most require an instantiated class, and none were easy to use when replacing dependencies.

How do you mock ES6 dependencies with Typescript?

Determined to avoid using explicit dependency injection, we wanted a testing library that would:

1. Maintain the freedom of Javascript testing.

Inside Javascript, anything can be anything. This makes testing specific functionality easier, as test setup can be as minimal as is needed:

It doesn’t matter what item is at runtime, while testing you can configure it to test the specific behaviour of selectItem().

2. Natively and seamlessly handle ES6 features

We were building a brand new product using ES6+ features throughout the application. All of our files contained classes, all of our promises were handled using async/await. Yet when writing tests we were dropping into pre-ES6 code.

We needed a library that didn’t feel ‘hacky’ when mocking out classes and which worked natively with import syntax. Writing tests needed to feel like a first party experience, rather than fighting to remember how code used to be written before all the pretty new tools came along.

3. Create mocks without requiring instantiation

We needed to mock out classes before they were instantiated. Our code would fail in the constructor of the class. Changing the implementation of all of our files so that they would play nice with the testing library was not a solution we were happy with.

Our code basically did this under test.

4. Create stub implementations

The key use-case we were looking at was making dependencies go away.

We needed a library that would create full, stub versions of mocked classes. Maintaining fake implementations of our code was untenable. We needed something that would create an object that had the same shape as the mocked class, but would simply do nothing.

5. Maintain type safety

Typescript is wonderful because it gives you static types! In Javascript! No longer do you have to build and run your code, only to find that you forgot to change the name of that variable or that you misspelled that function.

We needed a testing library that made use of these features. Too many test files were littered with as any;. We were starting to run into issues where tests were falling over because the function name had changed in the code but not in the test. This was a scenario that Typescript should have been able to detect.

“Storm Trooper vinyl figure under desk lamp” by James Pond on Unsplash

The Solution: ts-mock-imports

There were many testing libraries that fit three, maybe four of the required behaviours. None fit all five.

Until this one.

ts-mock-imports creates a mock of an entire class, replacing all functions with no-op functions (functions that return undefined). This ensures none of the original code runs. It maintains type safety, ensuring that compile time errors are thrown if methods on the original class are updated without updating the tests.

It is built on top of sinon and designed to be lightweight. It handles the dependency injection part of your code, and easily plugs into a range of existing testing environments.

How to use

Install the library into your project.

npm install ts-mock-imports --save

Import the module you would like to replace. Ensure that the /path/to/file points at the same location that the file imports the dependency from (i.e. don’t point at /path/to/index in one place and then /path/to/file in another.

import * as module from './path/to/file';

Mock out your class, and save the manager so you can have control over the mocked class. Add the name of the class in quotes if it’s not the default export. If it is the default, no name is necessary.

var manager = ImportMock.mockClass(module, 'Service');var manager = ImportMock.mockClass(defaultExportModule);

Use the manager to control what is returned by various functions throughout your tests.

// To configure module.foo() to return { bar: 'bar' }
manager.mock('foo', { bar: 'bar' });

Be sure to restore your mocks at the end of your tests. This replaces the imports back to their original values.

manager.restore();

Example:

Assuming we have a class that throws an error when instantiated while testing:

And another service that depends on the first:

We can now test SelectService in a way that is simple and clean and with good control over the mock behavior. The tests no longer throw an error on instantiation, and everything works as expected:

In Conclusion

ts-mock-imports uses the type safety of Typescript while still leveraging the fuzzy runtime types of Javascript. This ensures that your test suite is easy to set-up and maintain, without forcing complexity on your existing app.

The library can be found here: ts-mock-imports.

“round brown and teal smiley print” by Devin Avery on Unsplash

Full disclosure: I am the creator of ts-mock-imports and therefore I think it is pretty great. Please reach out to me if you find any issues or want to see any new features.

Emma McMillan

Written by

A Kiwi who fell a little in love with computer programming.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade