Expect fs.watch callback to be called in Jasmine - javascript

I have a class that accepts a callback in it's constructor. It also has a method loadFile that gets the content from the file, modifies it a bit and calls the callback with the modified content. Up to this point a class can be easily tested.
Now, I want to add a watch feature. loadFile will do what I just described and will start watching a file. Every time the file changes, the process will be repeated (load the file -> modify the content -> call the callback). I try to test it using Jasmine but I cannot make it work.
The simplified code looks like this:
const path = require('path')
const watch = require('fs').watch
const write = require('fs').writeFile
class Loader {
constructor (callback) {
this.callback = callback
}
loadFile (file) {
watch(file, (p, e) => {
console.log('path changed', p, e)
// do some important stuff here
this.callback(p)
})
}
}
describe ('Loader', () => {
var loader
var callback
beforeEach(() => {
callback = jasmine.createSpy('callback')
loader = new Loader(callback)
})
it('converts a markdown file', () => {
// this file already exists
const file = path.join(__dirname, 'md', 'header.md')
loader.loadFile(file)
write(file, 'hello', 'utf8')
expect(callback).toHaveBeenCalled()
console.log('end of test')
})
})
The command line result is:
Started
end of test
path changed change header.md
path changed change header.md
F
Failures:
1) Loader converts a markdown file
Message:
Expected spy callback to have been called.
Stack:
Error: Expected spy callback to have been called.
at Object.it (/path/to/spec/callback.spec.js:34:22)
1 spec, 1 failure
Finished in 0.015 seconds
This test does not load actual content, I simplified it to show the problem I encountered: the callback is called, but after the expectation.
I've spent a lot of time figuring out what is wrong but I failed. I think I've tried every possible combinations of using done() that Jasmine provides, but I didn't have any luck. I would appreciate any help with this.

Related

Testing a file that calls its own function

I have a file that is the entry-point to my application;
const main = async () => {
// Do stuff
}
main().then();
I understand that I can export the main function, delete the call in the first file and in a separate file import and call the function there;
const entry = require('./rewritten-entry.js');
entry.main().then();
But for arguments sake lets say I really want to test this first file's main function. As soon as I import the file in my test, the main function will execute before I even reach the test code;
const entry = require('./original-entry.js') // <-- main function executes here
const test = {
// Do test stuff
}
For a test, I don't want this to happen. Likewise I don't want to mock the function since it is exactly what I want to test.
I see this common 'init' logic all over the place so I imagine there is a way to test this.

Trying to export function without success with JavaScript, Node.js

I'm trying to learn Node.js and I have a pretty good app now, but I wanted to implement a library (venom-bot) to send some text messages. My problem is that I'm having trouble trying to use functions outside the primary file. Here is my code:
// Dependencies
const server = require("./lib/server")
const venom = require("venom-bot")
// Declare the app
const app = {}
// Init function
app.init = () => {
// Start the server
server.init()
}
// Init venom-bot
venom.create().then((client) => start(client))
// A function to test if I can send the message
async function testing(msg) {
await msg.sendText(...some code here...)
}
function start(client) {
app.msg = client
// Here, if I pass app.msg as an argument, works
// My problem is that I can't use app.msg outside of here,
// even with the module.exports down there
// (I'm trying to use it on a helpers.js file).
testing(app.msg)
// Execute the application
app.init()
}
// Export the app
module.exports = app
On the helpers.js file I'm requiring it this way:
// Dependencies
const app = require("./index.js")
// A function to test
async function sendMsg(msg) {
await msg.sendText(...some code here...)
}
helpers.send = () => {
sendMsg(app.msg)
}
module.exports = helpers
Whenever helpers.send gets invoked, it should correctly use the async function right above the helpers.send passing the app.msg as argument, at least I think it should. What I'm missing here?
The error I got is:
(node:18148) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'sendText' of undefined
On testing purpose, I'm trying to simple call that helper when I receive a get request to a specified route.

After stubbing function still it calls the real function

I have stubbed the content of file, so I can run through only outer function
file.html
<body onload ="test('file.txt')">
<body>
file.js
const fs1 = require('fs');
let param;
module.export = {
test,
test1,
param
}
function test (outputParam) {
let fileData = test1(outputParam);
param = fileData;
}
function test1(outputParam) {
let data = fs1.readFileSync(outputParam);
return data;
}
Here as you see I load function test from html onload and in turn test1 calls and reads file, I have stubbed this file content as shown in the test below
When I run the test I want to see the variable param has the file content value
test.spec.js
let sinon = require("sinon");
let filejs = require('./file.js');
it('should run only the outer function' ,function() {
// I try to stub my function here
sinon.stub(filejs,'test1').callsFake ((someArg) => {
return "this is my file content";
});
// Now I will call my test function
filejs.test(someArg);
})
As you seen above I have stubbed function test1, still when I run the test I see test1 gets called and it reads the real file.
I am using mocha , I am new to stub or mock concepts, any suggestion is really appreciated.
You should probably try to stub readFileSync.
const fs = require('fs');
// ...
sinon.stub(fs, "readFileSync").callsFake ((someArg) => {
return "this is my file content";
});
Besides that, I can spot two issues with your code.
The real readFileSync will return a Buffer if called without a second parameter, not a string like your stub does.
The body onload event only exists inside the DOM. The fs module is only available in Node.js. If you run your code in a browser, you won't be able to use fs.readFileSync. If you run it in Node, your HTML file and the onload event won't be useful.

How to properly stub a function return value?

Premise: JS ES6, NodeJS
Testing Framework: TAP
Mocking Library: testdouble.js
I am attempting to mock the return value for the method of my class and keep receiving this error:
not ok Unsatisfied verification on test double. Wanted: - called with (true). But there were no invocations of the test double.
Here is my testing code:
// Imports for unit testing
const tap = require('tap');
const Subject = require('../src/iTunesClient.js');
const td = require('testdouble');
let reqJson;
// Ensure the iTunes class methods are called
tap.test('iTunesClient class methods function as intended', (t) => {
t.beforeEach((ready) => {
reqJson = td.replace('../src/reqJson.js');
ready();
});
t.afterEach((ready) => {
td.reset();
ready();
});
t.test('iTunesClient.getData', (assert) => {
const callback = td.function();
const subject = new Subject();
subject.setTerm('abc 123');
subject.setURL();
td.when(reqJson.get(td.callback)).thenCallback(true);
subject.getData(callback);
td.verify(callback(true));
assert.end();
});
t.end();
});
Specifically, this line is related to my issue:
td.verify(callback(true));
How can I fake the callback value of true for reqJson.get()? Right now, Subject.geData() is a method of the iTunesClient class which calls another file, reqJson.js, to use its exported get() method.
It's a little hard to tell from your example, but it looks like you're requiring iTunesClient before you call td.replace. In this case, the real reqJson module will be required and cached on line 3.
You need to call td.replace early enough to avoid this, e.g. in between requiring tap and iTunesClient.
I wanted to update this question, as I recently solved this issue. Essentially, I had two issues:
Account for both reqJson function parameters
Account for all callback return values
Per testdouble documentation for item 1:
When passed td.matchers.anything(), any invocation of that test double function will ignore that parameter when determining whether an invocation satisfies the stubbing.
Hence, I adjusted my line of code as follows:
Before: td.when(reqJson.get(td.callback)).thenCallback(true);
After: td.when(reqJson.get(td.matchers.anything(), td.callback)).thenCallback(null, null, null);

Stub save Instance Method of Mongoose Model With Sinon

I am trying to test a service function I use to save a widget using a Mongoose model. I want to stub out the save instance method on my model, but I cannot figure out a good solution. I have seen other suggestions, but none seem to be complete.
See... this, and this.
Here is my model...
// widget.js
var mongoose = require('mongoose');
var widgetSchema = mongoose.Schema({
title: {type: String, default: ''}
});
var Widget = mongoose.model('Widget', widgetSchema);
module.exports = Widget;
Here is my service...
// widgetservice.js
var Widget = require('./widget.js');
var createWidget = function(data, callback) {
var widget = new Widget(data);
widget.save(function(err, doc) {
callback(err, doc);
});
};
My service is very simple. It accepts some JSON data, creates a new widget, and then saves the widget using the "save" instance method. It then calls back passing an err and doc based on the outcome of the save call.
I only want to test that when I call createWidget({title: 'Widget A'})...
The Widget constructor is called once with the data I passed to the service function
The save instance method on the newly created widget object is called once
EXTRA CREDIT: That the save instance method calls back with null for the err and with {title: 'Widget A'} for the doc.
In order to test this in isolation, I would probably need to...
Mock or stub the Widget constructor so that it would return a mock widget object that I create as part of my test.
Stub the mock widget object's save function so I can control what occurs.
I am having trouble figuring out how to do this with Sinon. I have tried several variations found on the pages of SO with no luck.
NOTES:
I don't want to pass in an already constructed model object to the service because I want the service to be the only thing that "knows" about mongoose.
I know this is not the biggest deal (to just test this with more of an integration or end-to-end test, but it would be nice to figure out a solution.
Thanks for any help you can provide.
If were to test that, this is how I would approach it, first have a way to inject my mocked widget to the widget-service. I know there's node-hijack, mockery or something like node-di, they all have different styles, I'm sure there's more. Choose one and use it.
Once I get that right, then I create my widget-service with my mock widget module. Then I do something like this(this is using mocha btw):
// Either do this:
saveStub = sinon.stub();
function WidgetMock(data) {
// some mocking stuff
// ...
// Now add my mocked stub.
this.save = saveStub;
}
// or do this:
WidgetMock = require('./mocked-widget');
var saveStub = sinon.stub(WidgetMock.prototype, 'save');
diInject('widget', WidgetMock); // This function doesn't really exists, but it should
// inject your mocked module instead of real one.
beforeEach(function () {
saveStub.reset(); // we do this, so everytime, when we can set the stub only for
// that test, and wouldn't clash with other tests. Don't do it, if you want to set
// the stub only one time for all.
});
after(function () {
saveStub.restore();// Generally you don't need this, but I've seen at times, mocked
// objects clashing with other mocked objects. Make sure you do it when your mock
// object maybe mocked somewhere other than this test case.
});
it('createWidget()', function (done) {
saveStub.yields(null, { someProperty : true }); // Tell your stub to do what you want it to do.
createWidget({}, function (err, result) {
assert(!err);
assert(result.someProperty);
sinon.assert.called(saveStub); // Maybe do something more complicated. You can
// also use sinon.mock instead of stubs if you wanna assert it.
done();
});
});
it('createWidget(badInput)', function (done) {
saveStub.yields(new Error('shhoo'));
createWidget({}, function (err, result) {
assert(err);
done();
});
});
This is just a sample, my tests sometimes get more complicated. It happens that most of the time, the backend calling function(here it is, widget.save) that I want to mock, is the one that I want it's behavior to change with every different test, so that's why I reset the stub everytime.
Here's also another example for doing similar thing: https://github.com/mozilla-b2g/gaia/blob/16b7f7c8d313917517ec834dbda05db117ec141c/apps/sms/test/unit/thread_ui_test.js#L1614
Here is how I would do it. I'm using Mockery to manipulate the module loading. The code of widgetservice.js must changed so that it calls require('./widget');, without the .js extension. Without the modification, the following code won't work because I use the general recommended practice of avoiding extensions in require calls. Mockery is states clearly that the names passed to the require call must match exactly so.
The test runner is Mocha.
The code follows. I've put copious 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();
});
});
});
Unless I've missed something, this covers all the tests that were mentioned in the question.
With current version of Mongoose you can use create method
// widgetservice.js
var Widget = require('./widget.js');
var createWidget = function(data, callback) {
Widget.create(data, callback);
};
Then to test the method (using Mocha)
// test.js
var sinon = require('sinon');
var mongoose = require('mongoose');
var Widget = mongoose.model('Widget');
var WidgetMock = sinon.mock(Widget);
var widgetService = require('...');
describe('widgetservice', function () {
describe('createWidget', function () {
it('should create a widget', function () {
var doc = { title: 'foo' };
WidgetMock
.expects('create').withArgs(doc)
.yields(null, 'RESULT');
widgetService.createWidget(doc, function (err, res) {
assert.equal(res, 'RESULT');
WidgetMock.verify();
WidgetMock.restore();
});
});
});
});
Also, if you want to mock chained methods use sinon-mongoose.

Categories