Wildhoney / Mocktail

:tropical_drink: Mock all of your ES6 module components with Mocktail using dependency injection.
http://mocktail.herokuapp.com/
MIT License
103 stars 6 forks source link

The comparison to proxyquire doesn't mention noCallThru #2

Open 0xR opened 8 years ago

0xR commented 8 years ago

When you look at this https://github.com/thlorenz/proxyquire#preventing-call-thru-to-original-dependency

You can prevent the call thru for the Request.js from the example. This means no ajax calls will be performed. Doing this I don't see the difference with Mocktail and proxyquire.

Wildhoney commented 8 years ago

proxyquire is all about monkeypatching, which is a perfectly legitimate approach – it's the same approach that Jasmine's spies with spyOn takes.

With monkeypatching you're assuming that the items you wish to mock are available on the interface, however if you've abstracted away such details — for example by using a Proxy or by using the revealing module pattern then monkeypatching becomes unavailable, whereas Mocktail would continue to function.

I've used proxyquire myself — in fact a lot more than I've used my own Mocktail module — and continue to do so. I'm not claiming that Mocktail is superior in any way, rather it's a different approach to the same problem.

0xR commented 8 years ago

Ok, it would help to make clear this distinction in the readme.

Also explain how you would use a proxy, I don't understand it yet. The revealing module pattern seems unnecessary in es6 in which the examples are written. Maybe give examples on when to use your module and when to use proxyquire.

Wildhoney commented 8 years ago

Ponyfoo is currently doing a great series on ES6 features — take a look at the proxies in depth.

It's not easy for me to sum up succinctly in the README, but I'll try my best in here and then I can link to this issue.

Eventually everything is monkey-patchable in your code, but it's a case of where you do it.

For example, consider the following:

import {makeRequest} from './Request';

export default class DataProcessor {

    getData() {

        return new Promise(resolve => {

            const requests = [makeRequest('/users'), makeRequest('/places')];

            Promise.all(requests).then(response => {

                // Some logic to handle the response.
                // ...

                resolve({ users, places });

            });

        });

    }

}

The getData method is available on the interface, and is therefore capable of being monkey-patched — whereas the makeRequest method isn't available because it's abstracted away.

spyOn(dataProcessor.getData).and.callFake(() => ...);

With proxyquire you'd simply mock the getData method — but for that you'd also need to mock out all of the logic in the then callback. This is definitely one approach, and from my point of view is neither good nor bad — it gets the job done, and your code is — most importantly — tested. You'd also be wise to abstract away the logic as well, which could be testable in isolation provided it's available on the interface.

With Mocktail you'd instead add the mock method when you export your DataProcessor object (as I see it, a downside to Mocktail and I'd rather have a way of adding middleware to an import — think — God forbid — PHP's __autoload hook).

When running the getData method in production, the live makeRequest method would be used and AJAX requests would be sent to the server. However, when creating your unit-tests you'd configure the makeRequest method to instead use your mocked adaptation – which would presumably use setTimeout with some arbitrary timeout value.

Therein lies the advantage of Mocktail over proxyquire in this instance (I still maintain that proxyquire is a more robust solution). With Mocktail the makeRequest method is a fake, and therefore by invoking getData no AJAX requests are made. In a nutshell, you've monkey-patched the un-monkey-patchable by instead injecting a fake object into your live code — think Angular-esque dependency injection.

With the Mocktail approach you can now go ahead and invoke getData without duplicating its logic for your unit-tests. All logic remains centralised, and modifying your getData logic does not mean changing your getDataMock logic, because there is no such method — only a fake makeRequest.