Understanding state in jest tests that use spyOn - javascript

Suppose that I have a test file foo.test.ts that looks like this:
describe("foo", function() {
let myMock;
beforeAll(function() {
myMock = jest.spyOn(someObject, "someMethod");
});
test("my test", function() {
beforeEach(function() {
myMock.mockReturnValue("someValue");
});
...
});
afterAll(function() {
myMock.mockRestore();
});
});
I'm using this pattern because I've seen it recommended in other StackOverflow answers. I wish to understand how Jest works though.
Do I really need to store the mock in a variable, or can I do this?
describe("foo", function() {
test("my test", function() {
beforeEach(function() {
jest.spyOn(someObject, "someMethod").mockReturnValueOnce("someValue");
});
...
});
});
Notice that I'm using mockReturnValueOnce instead of mockReturnValue.
I ran both implementations and they work fine, but I wonder whether I can say that they are equivalent? Will the "someMethod" function, part of "someObject", not be retained as a mock across other tests from other files?

jest.spyOn() returns a jest mock function and you want to store the returned mock function in a variable so that you can make assertions.
If you don't store the mock function returned by jest.mock(), then how will you make assertions? All the methods, provided by jest, that can be called on a mock function, can't be called without having a reference to the mock function.
If you just want to mock a return value and not make any assertions on the mocked function, then you don't need to save the reference. Generally, when you mock a function, you want to make assertions like how many times it was called, what arguments it was called with, etc.
Will the "someMethod" function, part of "someObject", not be retained
as a mock across other tests from other files?
someMethod will only be mocked in the test file where you mock it. It won't affect the other test files.

Related

Sinon stubbed method no longer stubbed when using underscore delay

So I'm writing javascript unit test using sinon stubs. The stubbed method is called in the code is wrapped in a delay (underscore delay). However, the actual method is getting called rather than the stub. When I don't use delay, it works as expected and calls the stub. I've included an example below of what the code looks like. Any idea what might be going on? One hypothesis I have is that sinon is keeping track of references of someObject to restore the stub, and since I'm using a delay, someObject falls out of scope and the stub is restored to the actual method.
// This is code form the unit test.
// someObject.someFunction is stubbed
sinon.stub(someObject, 'someFunction', function() {
console.log('stubbed someFunction');
});
someObject.execute();
// These methods are defined within someObject
execute: function() {
_.delay(
function() {
this.someFunction();
},
1000
);
}
// This is called from the unit test despite being stubbed.
// However, when the _.delay() is removed, the stubbed
// method is called.
someFunction: function() {
console.log('real someFunction');
}

Using Jasmine to test a module calling another module

I'm trying to use Jasmine to test that some modular JavaScript is initiating as expected. But I'm going round in circles.
As a simplified example, I have these two files:
// myObj.all.js
;(function() {
window.myObj = window.myObj || {};
myObj.all = {
init: function() {
myObj.page.init();
}
};
}());
// myObj.page.js
;(function() {
window.myObj = window.myObj || {};
myObj.page = {
init: function() {
console.log('hello');
}
};
}());
And in the HTML I would call:
myObj.all.init();
to initialise everything. There are more modules in addition to myObj.page, and one call to myObj.all.init() initialises all of them.
I want to test that when myObj.all.init() is called, then myObj.page.init() is also called. I thought something like this would do it, but the test fails:
spyOn(myObj.page, 'init');
myObj.all.init({});
expect(myObj.page.init).toHaveBeenCalled();
How should I test this?
I have never used jasmine but an easy way to verify that methods are being called from within another method is to mock the methods that should be called.
It looks like jasmine has support for mocking.
In this case you could mock myObj.page.init() and all other methods that should be called from myObj.all.init(). The mocking should provide a method to assert that the mocked method was actually called.

Ember Data's store promises don't work in quint tests

Update
A bit of context into some quirks of the illustrative code below. StoreProxy exists as a model, created by the ApplicationRouter, that has a reference to the store. This lets other objects access the store directly (for singletons, tests, etc). Example:
MyApp.StoreProxy = DS.Model.extend();
MyApp.ApplicationRoute = U.Route.extend({
model: function () {
return this.store.createRecord('storeProxy');
}
});
Before the route is executed, StoreProxy doesn't have a store property. After, it does. I can only assume this is because of some ember-data magic.
I very well realize your reaction to this may be "Ugh! No! You're doing it wrong!". Noted. We'll move to do it the right way from here over time. That said, this is where the code is now. So, given that, and given this method for getting a reference to the current store, why doesn't the code below call its accept or rejection handlers?
Original question
I'm writing a qUnit unit test for ember. I'm using fixture data. The findAll call on the store isn't resolving or rejecting the promise.
test('Find all in store', function() {
expect(1);
var findPromise;
findPromise = MyApp.StoreProxy.store.findAll('rule');
findPromise.then(function(result) {
console.log('yes');
ok(true);
}, function(error) {
console.log('no');
});
});
I tried using async tests mentioned in this question:
testing ember fixture data with quint but the resolve and reject are never called, so the test hangs indefinitely.
I've also tried placing Ember.run calls around my code, in case it's a weird run loop thing. But to no avail.
asyncTest('Find all in store', 1, function() {
var findPromise;
Ember.run(function() {
findPromise = MyApp.StoreProxy.store.findAll('rule');
findPromise.then(function(result) {
console.log('yes');
ok(true);
start();
}, function(error) {
console.log('no');
start();
});
});
});
The code I'm testing runs fine when I run the application normally (fixture adapter or no), so it feels like something with the test environment.
Any thoughts on what to try? I'm stumped.
The way that you're writing your asynchronous tests is incorrect. Check out QUnit's page on async testing. Your test should look something like this:
asyncTest('Find all in store', function() {
var findPromise = ...;
findPromise.then(function(result) {
start();
ok(result);
}, function() {
start();
ok(false);
});
});
Specifically:
You put an extra parameter in the asyncTest function, which likely causes the test to not run at all.
You're using Ember.Application.store, which is not how you should access your store (and probably isn't even a valid store). I'm not sure what your context is, but you should be getting your store from elsewhere.
You're putting the start() calls after your assertions when they should be before.

Stubbing Date.now() and Math.random()

I'm using Mocha with Sinon to unit test my node.js modules. I've successfully mocked other dependencies (other modules that I've written), but I've run into problems stubbing non-pure functions (like Math.random() and Date.now()). I've tried the following (simplified so that this question isn't so localized), but Math.random() was not stubbed because of an obvious scope problem. The instances of Math are independent between the test file and mymodule.js.
test.js
var sinon = require('sinon'),
mymodule = require('./mymodule.js'),
other = require('./other.js');
describe('MyModule', function() {
describe('funcThatDependsOnRandom', function() {
it('should call other.otherFunc with a random num when no num provided', function() {
sinon.mock(other).expects('otherFunc').withArgs(0.5).once();
sinon.stub(Math, 'random').returns(0.5);
funcThatDependsOnRandom(); // called with no args, so should call
// other.otherFunc with random num
other.verify(); // ensure expectation has been met
});
});
});
So in this contrived example, functThatDependsOnRandom() would look like:
mymodule.js
var other = require('./other.js');
function funcThatDependsOnRandom(num) {
if(typeof num === 'undefined') num = Math.random();
return other.otherFunc(num);
}
Is it possible to stub Math.random() in this scenario with Sinon?
yes, this is an old question but it is valid. Here is an answer that works, though I'd love to hear suggestions on how to make it better.
The way I've dealt with this in the browser is to create a proxy object. For example, you can't stub the window object in the browser so you can create a proxy object called windowProxy. When you want to get the location you create a method in windowProxy called location that returns or sets windowLocation. Then, when testing, you mock windowProxy.location.
You can do this same thing with Node.js, but it doesn't work quite as simply. The simple version is that one module can't mess with another module's private namespace.
The solution is to use the mockery module. After initializing mockery, if you call require() with a parameter that matches what you told mockery to mock, it will let you override the require statement and return your own properties.
UPDATE: I've created a fully functional code example. It is on Github at newz2000/dice-tdd and available via npm. /END UPDATE
The docs are pretty good, so I suggest reading them, but here's an example:
Create a file randomHelper.js with contents like this:
module.exports.random = function() {
return Math.random();
}
Then in your code that needs a random number, you:
var randomHelper = require('./randomHelper');
console.log('A random number: ' + randomHelper.random() );
Everything should work like normal. Your proxy object behaves in the same way as Math.random.
It is important to note that the require statement is accepting a single parameter, './randomHelper'. We'll need to note that.
Now in your test, (I'm using mocha and chai for example):
var sinon = require('sinon');
var mockery = require('mockery')
var yourModule; // note that we didn't require() your module, we just declare it here
describe('Testing my module', function() {
var randomStub; // just declaring this for now
before(function() {
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false
});
randomStub = sinon.stub().returns(0.99999);
mockery.registerMock('./randomHelper', randomStub)
// note that I used the same parameter that I sent in to requirein the module
// it is important that these match precisely
yourmodule = require('../yourmodule');
// note that we're requiring your module here, after mockery is setup
}
after(function() {
mockery.disable();
}
it('Should use a random number', function() {
callCount = randomStub.callCount;
yourmodule.whatever(); // this is the code that will use Math.random()
expect(randomStub.callCount).to.equal(callCount + 1);
}
}
And that is it. In this case, our stub will always return 0.0.99999; You can of course change it.
It is easy to stub Date.now() with sinon by using Fake timers :
Fake timers provide a clock object to pass time, which can also be used to control Date objects created through either new Date(); or Date.now(); (if supported by the browser).
// Arrange
const now = new Date();
const clock = sinon.useFakeTimers(now.getTime());
// Act
// Call you function ...
// Assert
// Make some assertions ...
// Teardown
clock.restore();
Are you sure that not mocking Math is the problem. It seems that this line make not much sense:
sinon.mock(other).expects('otherFunc').withArgs(0.5).once();
you mock others in one module but use it in another one. I dont think that you will get the mocked version in mymodule.js. On the other hand stubbing Math.random should work, as this is global for all modules.
Also take a look at this SO for mocking dependencies in nodeJS tests.

Why do I have to call spyOn in a beforeEach()?

I have a simple test suite that has one it function inside of it. I want to see if a certain function is called within the function I'm calling, so I have something like this:
describe("doStuff", function () {
var foo = new Foo();
spyOn(foo, "doOtherStuff");
foo.doStuff(true);
it("should do stuff and other stuff", function() {
expect(foo.stuffDone).toBe(true);
expect(foo.doOtherStuff).toHaveBeenCalled();
});
});
However, this gives me the error: Expected a spy, but got Function.
After looking around some, I saw all examples had the spyOn in a beforeEach. So, I changed my test to:
describe("doStuff", function () {
var foo = new Foo();
beforeEach(function() {
spyOn(foo, "doOtherStuff");
foo.doStuff(true);
});
it("should do stuff and other stuff", function() {
expect(foo.stuffDone).toBe(true);
expect(foo.doOtherStuff).toHaveBeenCalled();
});
});
And this works. I'm pretty new to jasmine, so I may just be missing something obvious, but I just want to know why it has to be in a beforeEach for the spyOn to work. It's easy enough to just use the beforeEach, but I want to understand better what is going on. Thanks.
That is simply because Jasmine runs the Specs in a different closure. The describe and it calls only register callbacks that are added to a queue and then executed by Jasmine later. And Jasmine always cleans up the spies ...
But you can also add the spyOn to the it callback.

Categories