For more than 10 years, _big__ of my work is pure JavaScript. But sometimes you have to deal with a little more typed languages. And each time after such a contact, there remains a strange feeling of “incomprehensibility”, so why can't it be done in the same way in js.
Especially because you can. And today we will try to add a bit of typing to the most chaotic javascript area — mock-and in unit tests.
Others say that
mocking is a coding smell .
')

"Not" right moki
What are the main arguments against mocks? In short:
Mocking is required when our decomposition strategy has failed.
- as Eric Elliot spoke from the link above.
And if in Russian -
- Your hands are crooked
- Use DI!
- Moki - they are not honest
There is only one problem - nodejs
is an IoC framework, and the current version of the modular system is DI. In principle, this is why popular libraries for mocks - proxyquire, mockery and others, generally work in principle.
That is, technically - mocking is not a coding smell.
But there is another side to the question - the libraries themselves "smell" so much that they infect the rest of the code a little. Proxyquire is just crooked, mockery is too slow, mocks in the jest are especially beautiful. I gave some review of this in the May
article about rewiremock .
"Right moki"
The "correctness" of the moka, that of the dough, is very easy to determine - it must correctly fall, and must fall in the right place. If the curve is mock, the mock should fall, not expect (someFunction) .to.be.called (). After all, sometimes you can guess for so long why the method is not called when everything should be fine.
Proxyquire had a good command — callThrough, which allows you to use the “real” file as the base. But it wasn’t recommended to use it, because it’s difficult to control which exports were locked up and which were not. The fact that it is difficult to control the file process entirely - even did not pay attention.
Jest has a very convenient automatic mocks __mock__ system that allows you to transparently provide mocks for real files ... in a slightly difficult to control form. Or all or nothing. Well, no one controls these moki.
The main problem of mocks is very easy to describe with one
wildest tslint example , the no-export-by-default rule:
// app.ts import ActionPopover from "./action"; // action.ts export default class ActionMenu { ... } // oops, we just renamed this from popover -> menu but our consumer had no idea!
In the case of mocks, it will be even more fun - the tests will remain green, since they will work with the mock, which both worked and works, but the real file - the real file can do anything -
for tests it does not exist .

This is the main problem, and the main coding smell from mocks. Well, they do not add confidence in the future.
Type safety
I myself encountered such a situation a couple of times, and for the third I decided to just close it technically. More specifically,
rewiremock v3 supports static typing of mocks for Flow / JS and shallow export checking for regular JS.
TypeScript vs Flow?It took me about a week to write 10 lines of TS / Flow that would do all the work, while I spent 90% of the time on Flow, and another week to write 10 more lines, while 90% of the time I spent on TS. Friendship won.
So - let's start with an example.
var foo = proxyquire('./foo', { 'fs': { fileWroteSync } });
Proxyquire does exactly what it says - overloading dependencies. But it does not check what it is actually doing there - that fs will be requested, that the function is intercepted, that the operation is valid.
The mockery is still worse, as the mocks are advertised and their use is slightly distributed in space.
mockery.registerMock('fs', fsMock); mockery.enable(); const mocked = require(...)
The rewiremock is no better at all. Given that the base API roughly repeats the mockery interface.
// API - . / rewiremock('./b.js').withDefault(overrideDefault).with({ helper:2});
This is the usual way to create mock. Whether on the drum it has a default export for the file, and whether there is an export with the name helper, which is also a number. As described above, the difference between the mock and the real file that it should replace, and there is a problem, and there is a main source of “smell”.
There are two solutions to this situation:
The first uses dynamic imports, which for TS / Flow return tized results of promises, information from which can be used in the future only through static typing. The underlying code is only required to support the ability to set the name of the import mock. Hi asynchrony :(
// API - import, name-resolution rewiremock(() => import('./b.js')) .withDefault(overrideDefault) // default .with({ helper: 42 }) // c
Moreover, all the necessary information is available for IntelliSence or another autocompiler. Pure TypeScript in action (well, or Flow). This not only makes moki “better”, but also improves “user experience” when writing mocks. IDE will help and prompt.
The second option is simpler, but it can only work in runtime, including without any problems it can be ported to almost any other libraries, since the code there is just three lines.
rewiremock('./b.js') .with() .toMatchOrigin();
In this case, before the operation of replacing a real file with a mock (somewhere deep in require), the actual file will actually be loaded, and its exports will be compared with this mock for the type names match.
The main advantage of this option - it works synchronously - as a result, it is convenient to work with it, while the use of dynamic imports requires the use of asynchronous APIs, which is not very convenient :(
PS: there is a problem with the “typing” of the good old synchronous require, so you should not even try.
I will be honest - the only thing that improves the typing of mocks is the time of writing mocks (if there is typing) and finding bugs (in both cases). I was just wondering if it was possible to do this in principle, and I basically did it.
Jest
Jest is so good that he even has his own system of mocks, sealed in his sandbox for file execution (which is one of the main features).
This system is two-faced, and consists of automok __mocks__, and manual mokov. The first are beautiful, the second are terrible. Well, in the guts of Jest everything is bad and not entirely logical - it replaces with itself the modular system node.js, babel and everything else, filling the whole space and destroying all the "standard" infrastructure. For many, this is a fact - proxyquire with Jest does not work. Nothing works with jest.
But I would like to have typed mocks, and there is a solution.
This is a small cli
jest-typed-mock utility that scans directories for mocks and checks for compliance with real files.
Operating modes 4:
- jest-typed-mock flow, for Flow
- jest-typed-mock typescript, for TS
- jest-typed-mock javascript, for "strict" checking of JS (compares the number of arguments in functions)
- jest-typed-mock exports, to check only names and types.
I ran it on a couple of examples, and was able to find various “accumulated” discrepancies in the mocks and reliats, which were on the way to “green tests, but no prod”, but (thank God) have not yet reached. Honestly, I would like to see such a system built into Jest by default.
For reference: rewiremock is arrogant enough to move the jest and mock yourself. For this you need to perform only two actions -
// 1. . // "" rewiremock jest-runtime rewiremock.overrideEntryPoint(module); // 2. require "" require = rewiremock.requireActual;
This completely kills the jest for those files that will be requested - no sandboxing, jest as a local variable, of course, no jest mocks, especially __mocks__ ... But the order for this came.
Conclusion
For almost a year now I've been trying to cut rewiremock. It began as a replacement for proxyquire, continued as a replacement for mockery, and stomped somewhere further.
Every time I drink coffee in the shade of eucalyptus, I think - what new feature to add, and why. Then I understand that this is not possible ... and I want my own in a couple of months.
So it was with the isolation modes, with the work under the webpack, jest, and now with the typing. Well, if you want% username% something from a small library for mocks, you just know who to contact.
More on the link .