A long time ago I looked at Terminator1. Growing up a bit and looking at the slightly refined Terminator 2. We had to wait a little longer for the third interaction.
And every time poor SkyNet was not lucky, and every time everything was awry, and the secret of this bad luck is very simple - SkyNet was not
Subject Under Test ( SUT ) .
In other words, SkyNet has forgotten about testing, and made reconnaissance in force. And, as is usually the case, it fell into the crust (under a press, into a lava, then I don’t remember) approximately immediately after being released into production.
')
And the whole question - how to test something before this calculation, until it becomes too late to turn back. And kill all humans, of course.

Tdd
To begin with, we use the TDD (Test Driven Developement) technique to determine what needs to be done, and why. Those who watched movies know - everything is very simple:
- When Judgment Day comes, launch rockets and kill all humans.
- When John Connorr is born, dunk and make a movie about it.
There is only one problem - both of these events are, as it were, single - the rockets must be launched from the first time, and John should also be thrown from the first time, and then the resistance will lead. In general, if you do not have a time machine, then it’s not easy to write tests and make them
repeatable .
And now what i can do?
So, SkyNet has only one chance to launch rockets, only one chance to send the terminator back to the past, where he will have to do his job on the first attempt. Well - for this test, and there are.
And the only way to solve the problem is to somehow replace
real rockets with
unreal ones , so that they can be launched-restarted.
Scientifically, this is called
“locking” (mock)
The only question is how!
But to answer this question - in the beginning you need to look at the codes of the SkyNet itself.
The code goes - in order to test SkyNet, you need to replace the dependencies of the central file “dooms-day.js” with controlled entities.
So how do you lock?
I'm glad you asked. And I do not have one simple answer, but there are 5 even simpler ones. Another bunch of different libraries and a couple of anti-patern patterns on how to moisten well and how moocate badly.
But it’s better to start with a quick overview of popular libraries that provide
different interfaces for addicting.
Option 1 - in tests use “test” module variants
Sounds simple, and just really. You just need to create a "replacement", and "replace".
In general, "__mock__" is a very simple and convenient mechanism, which has only one drawback - Jest. This feature works only in Jest, and if Skynet uses mocha, ava or karma to run tests, you will have to look for other interfaces.
Option 2 - in the tests simply replace the real modules with “test”
If the magic of __mock__ is not available to us, then what about just a mock?
import {Launch} from './rocket-silo';
Again, a very simple way to replace dependencies for a test file, and again, only Jest.
In fact, this is where the Jest capabilities end, and despite the fact that these two approaches are quite primitive (and simple), this is about what you need. Everything else is possible - unredefited redefinitions.
Option 3 - I want to replace Schwarzneiger on Stallone
What if you can download any file, but with “changed” parts?
import proxyquire from 'proxyquire'; import sinon from 'sinon'; const Launch= sinon.stub() const case = proxyquire.load('./dooms-day.js',{ './rocket-silo', { Launch }, './doom-scheduler', { theDay: Promise.resolve() }); expect(Launch).toHaveBeenCalled();
This is how proxyquire works - one of the most ancient popular libraries about this business. Allows you to download any file with overlapping dependencies directly requested from this file.
The main advantage over jest.mock is that in one test you can have 100,500 different proxyquired files in different ways. Plus, various commands such as .callThough, which allows only partly to block the dependence (which is generally anti-fattern).
Option 4 is the same, only more declarative.
Proxyquire can sometimes be a bit annoying, plus occasionally you want something like __mock__, those having some “standard” mocks. All in all mockery!
import mockery from 'mockery'; import sinon from 'sinon'; mockery.registerMock('./rocket-silo', { Launch: sinon.stub() }); mockery.registerMock('doom-scheduler', { theDay: Promise.resolve() }); mockery.enable(); const {Launch} = require('./rocket-silo');
Mockery also has a powerful API, but a bit different than proxyquire. For example, there is an “isolation” mode when mockery starts swearing when “unresolved” files are connected, which can save you from accidental changes in dependency and logic.
Option 5 - The Last of the Magical
TestDouble.js is a library from a company of the same name that pays a lot of effort to the “correctness” of this or that approach. Technically, this is jest.mock, side view.
import td from 'testdouble'; const {Launch} = td.replace('./rocket-silo');
Moreover, the possibility of replacing arbitrary modules appeared
after a very very long dispute .
Generally TD - a storehouse of knowledge and best practices. I advise you to read the
section about moki .
But there are problems
- Jest moki are blunt and only work if you have a jest.
- Proxyquire works on snot. Library with zero feedback. Plus, many decisions that were logical at the time of creation - now cause a nervous tic (noPreserveCache)
- Mockery does not have all the features of Proxyquire, plus the isolation mode, one of the library features, is broken so that completely
- You never know what is mocking in Proxyquire or Mockery, since they are aggrieved by the exact name of the file, and we have a Babel.
- TD is commonly used by those who use TD, which is rare because of all sinon.
It is worth adding that all these libraries still handle cache badly, which sometimes leads to a serious slowdown of the tests.
For a large project, it takes about a second to require, since before and after the entire nodejs cache (and Babel) is erased
So how to test SkyNet?
Do not forget - the whole problem is that SkyNet wanted perfection. And all the disassembled tools are not perfect. If we start writing tests using them, SkyNet will send terminators from the beginning for us.
In order not to get to the counter, I wanted moki like in the jest, just under ava. The proxyquire syntax can often be convenient, as can the automatic jest.mock / td.replace. I wanted a lot of things, so I took the time machine back from work and a year ago I laid out a library that can do all of the above.
Replace jest.mock
import rewiremock from 'rewiremock'; import {Launch} from './rocket-silo'; import {theDay} from './doom-scheduler' import './dooms-day.js';
Replace mockery
import rewiremock from 'rewiremock'; import sinon from 'sinon';
Replaces proxyquire
import rewiremock from 'rewiremock'; import sinon from 'sinon'; const Launch = sinon.stub()
Replaces TD
import rewiremock from 'rewiremock'; const {Launch} = rewiremock('./rocket-silo').mockThrough();
At the same time, it works everywhere - mocha, ava, karma under node.js or webpack. At the same time, it works completely differently with the cache, without deleting anything that is not affected by the tests (sometimes 100 times faster). And there is always an API, for example, to be sure that mocks are used correctly:
import rewiremock from 'rewiremock'; import sinon from 'sinon'; rewiremock('./rocket-silo') .with({ Launch: sinon.stub()}); rewiremock('./rockets') .toBeUsed();
Or to replace one module with another (as mockery can)
import rewiremock from 'rewiremock'; import sinon from 'sinon'; const Launch = sinon.stub() const case = rewiremock.proxy('./dooms-day.js',{ './rocket-silo': rewiremock.with({Launch}).toBeUsed().directChildOnly(),
Or to use import instead of require, which can be useful in terms of name resolution and type safery.
rewiremock(() => import('doom-scheduler')).with({ theDay: Promise.resolve() });
In general, rewiremock is the dependency change library that will fit SkyNet. Especially given the constant use of the time machine to improve the work and release of new versions.
And, most importantly, this is your current library. Interface compatible.
And now my mission is to somehow transplant people with proxyquire and mockery (and smaller products) into something more usable. Just because old Arnie was “old, but not obsolete” in 2015, and hasn’t received updates since. Like proxyquire, like mockery.
For reference:
→
Jest mocks→
mockery→
Proxyquire→
TD.js (forget to read the wiki page)
And most importantly:
rewiremockPS: And this is not the first article about this library - the
rest can also be useful.