This is how I do it. I use Mockery to control module loading. The widgetservice.js code must be modified so that it calls require('./widget'); without extension .js . Without change, the following code will not work, because I use the general recommended practice of excluding extensions in require calls. Mockery clearly states that the names passed to the require call must match exactly that.
Test runner Mocha .
The following is the code. I wrote a lot of comments in the code itself.
var mockery = require("mockery"); var sinon = require("sinon"); // We grab a reference to the pristine Widget, to be used later. var Widget = require("./widget"); // Convenience object to group the options we use for mockery. var mockery_options = { // `useCleanCache` ensures that "./widget", which we've // previously loaded is forgotten when we enable mockery. useCleanCache: true, // Please look at the documentation on these two options. I've // turned them off but by default they are on and they may help // with creating a test suite. warnOnReplace: false, warnOnUnregistered: false }; describe("widgetservice", function () { describe("createWidget", function () { var test_doc = {title: "foo"}; it("creates a widget with the correct data", function () { // Create a mock that provides the bare minimum. We // expect it to be called with the value of `test_doc`. // And it returns an object which has a fake `save` method // that does nothing. This is *just enough* for *this* // test. var mock = sinon.mock().withArgs(test_doc) .returns({"save": function () {}}); // Register our mock with mockery. mockery.registerMock('./widget', mock); // Then tell mockery to intercept module loading. mockery.enable(mockery_options); // Now we load the service and mockery will give it our mock // Widget. var service = require("./widgetservice"); service.createWidget(test_doc, function () {}); mock.verify(); // Always remember to verify! }); it("saves a widget with the correct data", function () { var mock; // This will intercept object creation requests and return an // object on which we can check method invocations. function Intercept() { // Do the usual thing... var ret = Widget.apply(this, arguments); // Mock only on the `save` method. When it is called, // it should call its first argument with the // parameters passed to `yields`. This effectively // simulates what mongoose would do when there is no // error. mock = sinon.mock(ret, "save").expects("save") .yields(null, arguments[0]); return ret; } // See the first test. mockery.registerMock('./widget', Intercept); mockery.enable(mockery_options); var service = require("./widgetservice"); // We use sinon to create a callback for our test. We could // just as well have passed an anonymous function that contains // assertions to check the parameters. We expect it to be called // with `null, test_doc`. var callback = sinon.mock().withArgs(null, test_doc); service.createWidget(test_doc, callback); mock.verify(); callback.verify(); }); afterEach(function () { // General cleanup after each test. mockery.disable(); mockery.deregisterAll(); // Make sure we leave nothing behind in the cache. mockery.resetCache(); }); }); });
If I missed something, this covers all the tests that were mentioned in the question.
Louis
source share