ES6 class jest mocking - node.js

I have an ES6 class which I need to mock it's methods. Following the documentation i made a manual mock of this, and got the constructor to both be called and asserted.
My function that consumes this class is just a basic function that runs one of the class methods.
test.js
const mockConnect = jest.fn();
const mockAccess = jest.fn();
jest.mock('../../src/connection');
const connection = require('../../src/connection').default;
connection.mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true),
};
});
caller_function();
expect(connection).toHaveBeenCalled(); // works properly as the constructor is called
expect(connection).toHaveBeenCalledWith('something'); // works
expect(mockAccess).toHaveBeenCalled(); // says it was not called when it should have
caller_function.js
import connection from 'connection';
const conn = new connection('something');
export function caller_function() {
conn.access(); // returns undefined when mock says it should return true
}

This is happening because you're using mockImplementation() instead of a manual mock or the factory parameter to jest.mock(), and your mocked object is being created during the module loading process, since the constructor call is not inside of any function. What's happening is:
The call to jest.mock('../../src/connection') runs and sets connection to be an automatic mock.
The conn object is created using the automatic mock. Therefore its access method returns undefined.
The call to mockImplementation() happens, changing the connection mock. However, since the conn object has already been created, it doesn't get the custom implementation.
Moving the constructor call into caller_function is one way to fix it:
export function caller_function() {
const conn = new connection('something');
conn.access();
}
You could also use the factory parameter to jest.mock(), specifying the implementation there, instead of calling mockImplementation(). That way you won't have to change your implementation code:
const mockConnect = jest.fn();
const mockAccess = jest.fn();
import connection from '../../src/connection';
jest.mock('./so-import', () => {
return jest.fn().mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true)
};
});
});
...
BTW the convention for ES6 class names is to begin with an uppercase letter. I was temporarily confused by the lowercase name connection.

Did you try doing connection.mockClear(); before you write a mockImplementation for the methods?
Also please refer to this https://jestjs.io/docs/en/es6-class-mocks

Related

Handle async imports in jest.mock factory

I am reusing db mocks in different tests so I created aka factories for mocking a single table and calling them in jest.mock()
jest.mock('db', () => {
const {
UsersMockFactory,
RequestMockFactory
} = jest.requireActual('../../../../mocks');
return {
Users: UsersMockFactory(),
Requests: RequestMockFactory(),
};
});
The problem is that 19 tests will pass with mock from that file but 20th will throw an error RequestMockFactory is not a function.
I've tried using const mocks = require('../../../../mocks') and then console.log the result but RequestMockFactory still wasn't there for some reasons. I don't know why but it is the main reason I have this problem. I've tried to use await import() like this and it imports correctly:
jest.mock('db', async () => {
const {
UsersMockFactory,
RequestMockFactory,
} = await import('../../../../mocks');
return {
Users: UsersMockFactory(),
Requests: RequestMockFactory(),
};
});
Now the problem is that mock factory returns a Promise for an entire mock and obviously methods on a model are undefined.
I've tried to mock inside describe() , beforeAll(), inside it() - jest.mock is not working there.
How to handle imports in jest.mock without such pain?

Export resolved promise result

Consider I have a.js with following class
class Connector {
constructor (url) {
this.url = url;
this.conneciton = null;
}
async connect() {
this.connection = await someThidPartyModule.connect(url);
return this;
}
}
// here I would like to do something like
// export default new Connector().connect();
Then use in b.js, c.js etc. connection from resovled connect method:
import Connector from 'a.js';
Connector.connection.callSomeMethod(); // here connection already exists after that promise resolved
As far as I aware it is not possible to do this, but maybe the some hack or workaround exists?
So after some tries found following:
export Promise is not a good idea, as on each import we also will get Promise, event if it is already resolved
Better to export class instance, call connect method and then allow to use it in all other files
Sadly anyway promise moving up to main endpoint file, where we have to make initialization async function and inside wait for promise to resolve
Also tried to export class with static create method, but that leave us with instance on the far end, which cannot be exported to other files.

How do you use distinct with mockingoose?

I'm using Mockingoose to mock my mongoose calls when running tests with Jest. I tried this but I get an error
mockingoose.Account.toReturn(
["593cebebebe11c1b06efff0372","593cebebebe11c1b06efff0373"],
"distinct"
);
Error:
ObjectParameterError: Parameter "obj" to Document() must be an object, got 593cebebebe11c1b06efff0372
So then I try passing it an array of document objects but it just returns the documents. How di I get it to return just an array or strings?
Here's the code inside the function I'm testing:
const accountIDs = await Account.find({
userID: "test",
lastLoginAttemptSuccessful: true
}).distinct("_id");
I'm open to other ways of mocking my mongoose calls if someone knows of a better way. Thanks!
You can't.
My bad. I looked into mockingoose implementation and realized, it kind of "supports" distinct by implementing a mock, but it actually returned just the given documents, as for the other operations.
Opened a pull request for this issue and added a test, so you're example should be valid and working.
I think the answer is to not use mockingoose. You can do it pretty easily with jest alone.
You can use jest.spyOn() and then mockImplementation() to mock the first call like find() and update(). Here's an example of findOneAndUpdate() where we're checking to make sure the correct object is passed:
// TESTING:
// await Timeline.findOneAndUpdate(query, obj);
//
const Timeline = require("./models/user.timeline");
...
const TimelineFindOneAndUpdateMock = jest.spyOn(Timeline, "findOneAndUpdate");
const TimelineFindOneAndUpdate = jest.fn((query, obj) => {
expect(obj.sendDateHasPassed).toBeFalsy();
expect(moment(obj.sendDate).format()).toBe(moment("2018-11-05T23:00:00.000Z").format());
});
TimelineFindOneAndUpdateMock.mockImplementation(TimelineFindOneAndUpdate);
If you want to mock a chained function you can have it return an object with the next chained function you want to call. Here's an example of how to mock a chained distinct() call.
// TESTING:
// let accountIDs = await Account.find(query).distinct("_id");
//
// WILL RETURN:
// ["124512341234","124512341234","124512341234"]
//
const Account = require("./models/user.account");
...
const AccountFindMock = jest.spyOn(Account, "find");
const AccountFindDistinctResult = ["124512341234","124512341234","124512341234"];
const AccountFindDistinctResult = jest.fn(() => AccountFindDistinctResult);
const AccountFindResult = {
distinct: AccountFindDistinct
};
const AccountFind = jest.fn(() => AccountFindResult);
AccountFindMock.mockImplementation(AccountFind);
And after your test runs, if you want to check how many times a function is called like how many times distinct() was called you can add this:
expect(AccountFindDistinct).toHaveBeenCalledTimes(0);

nodejs - Export queries result to other files

I'm doing some query to retrieve some data from a database, and trying to export said data to be used in my nodejs aplication. But everything I've tried so far, does not work.
r.js
async function site() {
var test = await db
.select("*")
.from("site_credentials")
.then(data => {
return data;
});
return test;
}
module.exports = { user: site().then(data=>{return data})}
but I always get Promise pending. Even when I do the imports:
import users = require("./r")
users.then(data=>{return data})
and still doesnt work. How can I fix this?
Thank you,
For starters, there's no reason to resolve a promise and immediately return the same object resolved in its then block. Just omit the "then" if there is nothing else you need to do.
So this:
async function site() {
var test = await db
.select("*")
.from("site_credentials")
.then(data => {
return data; <--- this isn't necessary. Only adds noise unless there is something else you need to do. It's similar to "catching" and immediately "rethrowing" an error... just pointless
});
return test;
}
Can be this:
async function site() {
var test = await db
.select("*")
.from("site_credentials");
return test;
}
Secondly, I'm not really sure why you're trying to resolve it in the export. Just export the function.
module.exports = site;
Then when you require it elsewhere in your app, call it and resolve it there:
const users = require("./r")
users.then(data=>{
// do something with your data here...
})
Note that in your first example, you are exporting an object, containing a "users" property which is the function. If you do it that way, you would need to invoke it like so:
const users = require("./r")
users.users().then(data=>{
// do something with your data here...
})
You can see that users.users clearly doesn't make sense. So, export properly to avoid that. Export only the function itself, not nested inside some other object.
But, if you look closely, you'll notice another thing I did wrong. I'm exporting a "site" function, yet requiring it in as a "users" function. Naming conventions matter. If this function is called "site" here, you ought to require (or import depending on your module loader...) it in as "site"... thus:
const site = require('./r');
Otherwise you just confuse the crud out of a fellow developer.

sinon stub namespaced function

I'm having some issues using sinon stubs and it may stem from how I'm implementing namespacing on the module that I'm looking to stub. Methods directly defined on the prototype are stubbed as I would expect.
...my module.js
const Constructor = require('./constructor') //...just exports a singleton
/* Need to namespace some of my functions and retain the `this` context */
Object.defineProperty(Constructor.prototype, 'es', {
get: function() {
return {
method: require('./implementations/doesSomething.js').bind(this)
}
}
});
module.exports = Constructor;
/* ...testFile.js */
const Constructor = require('./constructor');
const instance = new Constructor();
const sinon = require('sinon');
sinon.stub(instance.es, 'method', function() {
return 'hijacked original method'
});
As mentioned on the Sinon issue tracker, the problem here is that using a plain Object.defineProperty(obj, 'prop') call does something else than plainly creating it using assignment (obj['prop'] = ...).
Generally, if you try defining your property without Object.defineProperty it will be stubbable, but using defineProperty (without creating a special configuration) will make it impossible to stub the property. The reason is simply that the default values for writeable and configurable are false! You cannot delete them or change them. And if you can't do that, then Sinon won't help you. So, generally, you need to add writeable: true, configurable: true in your property definition.
Now there was one more thing I forgot to answer originally:
You are not trying to wrap a function on Constructor.prototype.es.method - what you are trying to wrap is the function on the object returned by the getter on the property for es. That will never work. Why? Simply because the returned object is never the same. You are creating a new object around method each time. If you really need to replace/stub the method property, you actually need to replace the entire Constructor.prototype.es property instead. If you need this namespacing, you can vastly simplify this, and also enable stubbing, like this:
Constructor.prototype.es = {};
Object.defineProperty(Constructor.prototype.es, 'method', {
get: function() {
return someFunction.bind(this);
},
writeable: true,
configurable:true
}
An expanded, fully working example (Gist for download):
// constructor.js
const someFunction = function(){
return this.value;
}
function Constructor(){ };
Constructor.prototype.es = { value : 100 };
Object.defineProperty(Constructor.prototype.es, 'method', {
get: function() {
return someFunction.bind(this);
},
writeable: true,
configurable:true
});
// test.js
const instance = new Constructor();
console.log(instance.es.method()) // => 100
// using this won't work:
// sinon.stub(instance.__proto__.es, 'method').returns(42);
// because the getter is returning a _new_ function each time
// therefore you need to attack the actual getter function:
const stub = sinon.stub(instance.__proto__.es, 'method').value(()=>42);
console.log(instance.es.method()) // => 42
stub.get(()=>()=>84);
console.log(instance.es.method()) // => 84
stub.restore();
console.log(instance.es.method()) // => 100
// the above is working on the prototype, can't we do this on the instance?
// yes, we can, but remember that the `es` object is shared, so we
// can avoid modifying it by shadowing it further down the prototype
instance.es = { method: sinon.stub().returns(256) };
console.log(instance.es.method()) // => 256
delete instance.es
console.log(instance.es.method()) // => 100
<script src="https://unpkg.com/sinon#2.3.5/pkg/sinon.js"></script>

Resources