Friday, 26 July 2019

jest and just mock





mockup
https://jestjs.io/docs/en/manual-mocks
https://jestjs.io/docs/en/mock-functions
https://jestjs.io/docs/en/mock-function-api.html

the manual mock is very useful in a situation that you test code is testing fn1, inside of fn1, it calls fn2, and fn2 inside call fn3.   you have no control from your test code to replace fn2 and fn3, you can mock and jest will replace them for you.

Note: the key thing is that if you are mocking up a node_model e.g. mongo, then you should add __mock__ folder into the root folder (if your node_model is in the root folder as well)


you can also mock up without create __mocks__ and separate file. (just add it in your test file, at the top)


jest.mock('../config', () => ({
Config: class {
public static getxx() {
return '111';
}
public static getyy() {
return '22';
}
}
})
)


for manual mock mongo, here is my code:
https://github.com/wangpingsx/jesttest/blob/master/manualmock.test.ts

for mongo situation, you can also use mock function:

https://jestjs.io/docs/en/mock-function-api#mockfnmockclear

mongo mock with jest.fn solution, my code is here:
https://github.com/wangpingsx/jesttest/blob/master/fnmock.test.ts

if the code you want to test are :
public static async connectToDB() {
let dbClient = await MongoClient.connect(Config.MONGODB_URI);
let db = dbClient.db(Config.DB_NAME);
let collection = db.collection(Config.SUPPLIERISSUE_COLLECTION_NAME);
return { collection, dbClient };
}

this code is called indirectly from your test code.

in your test code (unit test) you just need to :

import { MongoClient } from 'mongodb';

after that you can mock them with a  default mock:


const mockCollection = {
findOneAndUpdate: {}
};

const mockDB = {
collection: {}
};

const mockDBclient = {
db: {},
close: () => {}
};

const resetMongoMock = () => {
MongoClient.connect = jest.fn(function(url, options, callback) {
console.log('aaaa');
return mockDBclient;
});
// MongoClient.connect = jest.fn();
// MongoClient.connect.mockReturnValueOnce(mockDBclient);
mockDBclient.db = jest.fn((dbName: string) => {
return mockDB;
});
mockDB.collection = jest.fn((collectionName: string) => {
return mockCollection;
});
mockCollection.findOneAndUpdate = jest.fn(async filter => {
return { value: { issueId: filter.issueId as string } };
});
};




then you in test you may want to  change the mock implementation in some test case and change it back. e.g. you want to make the connect function throw an error.

if you are using manual mock, then, it is ........ ok, you need to change the function in your test code like this:
MongoClient.connect = ()=> throw new Error("aaaa error");

the problem is that you need to set it back before next test. you have to set them again, but you already have all code in __mocks__, then you need write them again here..... not so good, i know you can optimise it, but there is a better way:

Don't use manual mock up in this case, use the function mockup way.

then, you setup all mocks before all test, then, when you want to do a special mock, then you just do :

// MongoClient.connect = async (dbString: string) => {
// throw new Error('cannot connect to db server!');
// };
MongoClient.connect.mockImplementationOnce(function(url, options, callback) {
throw new Error('cannot connect to db server!');
});

Note: above commented way doesn't work, because you are replacing the connect function (you made another mock), you need to chain up with the existing mock fn.

Then, the new mock will be used only once and it will go back to the previous mock.

Reset  VS. Restore VS. Clean

https://jestjs.io/docs/en/mock-function-api#mockfnmockreset

restore will remove mock. e.g. it will let your mongo use the actual mongo implementation.

reset will remove mock return and mock implementation. in our mongo situation. it will remove all your mock, and turn MongoClient to undefine. which is not very useful.

clean is not useful as well.

if our case we want to do add all basic mocks before all test, and change in tests change them back to the basic mocks.

you can turn the basic mocks into a function and call it in beforeAll and in beforeEach, call jest.resetallmocks  (this is a global function)  first and then call the basic mocks.

Or, just use  mockImplementationOnce.

mockImplementationOnce is very handy, especially in mongo mock case, because we chained our mocks, if you change the implement on the root then when you want to change them back, then you need to build the chain again. but if you just use mockImplementationOnce, the whole chain will be auto reset after the first call.




https://jestjs.io/docs/en/jest-object#jestresetallmocks


ES6 class mock,

here is the offical document for doing this:
https://jestjs.io/docs/en/es6-class-mocks

but if you are using typescript, you will find you can only use manaul mock, because jest.fn() cannot mock the constructor. typescript doesn't consider it is a constructor. it may work fine with js. but not typescirpt





Async

https://jestjs.io/docs/en/asynchronous#resolves-rejects



error:

https://jestjs.io/docs/en/expect#tohavebeencalledwitharg1-arg2-


atchUpsert(mockupjson as any).catch(e => expect(e.message).toMatch('cannot connect to db server'));


No comments:

Post a comment