I have a function which I'm trying to add unit tests for.
Function
async getStuff() {
const { foo } = this.props.data;
const { bar } = this.state;
const { bazz } = this.props;
const { x, y } = await helpers.getStuffFromServer(foo, bar, bazz);
this.setState({ x, y });
}
Test
it('should get stuff & set state', async() => {
const returnObject = {
x: dummyData.x,
y: dummyData.y
};
getStuff.mockReturnValue(() => returnObject);
const fakeBazz = jest.fn();
const wrapper = setup({ bazz: fakeBazz });
wrapper.instance().componentDidMount();
await expect(getStuff).toBeCalledWith(dummyData.foo, bar, fakeBazz);
console.log(wrapper.state());
});
My expect assertion works and correct asserts that getStuff was called with the correct values. But getStuff returns 2 x objects x & y - which it uses to setState. I've tried to mock the return value of getStuff, but these mocked return values are not being set in state.
Am I using mockReturnValue correctly?
Shouldn't you set it on the mock function:
fakeBazz.mockReturnValue(returnObject);
See: https://medium.com/#rickhanlonii/understanding-jest-mocks-f0046c68e53c
Related
I'm trying to pass a variable into a page.evaluate() function in Puppeteer, but when I use the following very simplified example, the variable evalVar is undefined.
I can't find any examples to build on, so I need help passing that variable into the page.evaluate() function so I can use it inside.
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
const evalVar = 'WHUT??';
try {
await page.goto('https://www.google.com.au');
await page.waitForSelector('#fbar');
const links = await page.evaluate((evalVar) => {
console.log('evalVar:', evalVar); // appears undefined
const urls = [];
hrefs = document.querySelectorAll('#fbar #fsl a');
hrefs.forEach(function(el) {
urls.push(el.href);
});
return urls;
})
console.log('links:', links);
} catch (err) {
console.log('ERR:', err.message);
} finally {
// browser.close();
}
})();
You have to pass the variable as an argument to the pageFunction like this:
const links = await page.evaluate((evalVar) => {
console.log(evalVar); // 2. should be defined now
…
}, evalVar); // 1. pass variable as an argument
You can pass in multiple variables by passing more arguments to page.evaluate():
await page.evaluate((a, b c) => { console.log(a, b, c) }, a, b, c)
The arguments must either be serializable as JSON or JSHandles of in-browser objects: https://pptr.dev/#?show=api-pageevaluatepagefunction-args
I encourage you to stick on this style, because it's more convenient and readable.
let name = 'jack';
let age = 33;
let location = 'Berlin/Germany';
await page.evaluate(({name, age, location}) => {
console.log(name);
console.log(age);
console.log(location);
},{name, age, location});
Single Variable:
You can pass one variable to page.evaluate() using the following syntax:
await page.evaluate(example => { /* ... */ }, example);
Note: You do not need to enclose the variable in (), unless you are going to be passing multiple variables.
Multiple Variables:
You can pass multiple variables to page.evaluate() using the following syntax:
await page.evaluate((example_1, example_2) => { /* ... */ }, example_1, example_2);
Note: Enclosing your variables within {} is not necessary.
It took me quite a while to figure out that console.log() in evaluate() can't show in node console.
Ref: https://github.com/GoogleChrome/puppeteer/issues/1944
everything that is run inside the page.evaluate function is done in the context of the browser page. The script is running in the browser not in node.js so if you log it will show in the browsers console which if you are running headless you will not see. You also can't set a node breakpoint inside the function.
Hope this can help.
For pass a function, there are two ways you can do it.
// 1. Defined in evaluationContext
await page.evaluate(() => {
window.yourFunc = function() {...};
});
const links = await page.evaluate(() => {
const func = window.yourFunc;
func();
});
// 2. Transform function to serializable(string). (Function can not be serialized)
const yourFunc = function() {...};
const obj = {
func: yourFunc.toString()
};
const otherObj = {
foo: 'bar'
};
const links = await page.evaluate((obj, aObj) => {
const funStr = obj.func;
const func = new Function(`return ${funStr}.apply(null, arguments)`)
func();
const foo = aObj.foo; // bar, for object
window.foo = foo;
debugger;
}, obj, otherObj);
You can add devtools: true to the launch options for test
I have a typescript example that could help someone new in typescript.
const hyperlinks: string [] = await page.evaluate((url: string, regex: RegExp, querySelect: string) => {
.........
}, url, regex, querySelect);
Slightly different version from #wolf answer above. Make code much more reusable between different context.
// util functions
export const pipe = (...fns) => initialVal => fns.reduce((acc, fn) => fn(acc), initialVal)
export const pluck = key => obj => obj[key] || null
export const map = fn => item => fn(item)
// these variables will be cast to string, look below at fn.toString()
const updatedAt = await page.evaluate(
([selector, util]) => {
let { pipe, map, pluck } = util
pipe = new Function(`return ${pipe}`)()
map = new Function(`return ${map}`)()
pluck = new Function(`return ${pluck}`)()
return pipe(
s => document.querySelector(s),
pluck('textContent'),
map(text => text.trim()),
map(date => Date.parse(date)),
map(timeStamp => Promise.resolve(timeStamp))
)(selector)
},
[
'#table-announcements tbody td:nth-child(2) .d-none',
{ pipe: pipe.toString(), map: map.toString(), pluck: pluck.toString() },
]
)
Also not that functions inside pipe cant used something like this
// incorrect, which is i don't know why
pipe(document.querySelector)
// should be
pipe(s => document.querySelector(s))
I am new to using Jest for unit tests. How can I mock this simple http request method "getData"? Here is the class:
const got = require("got")
class Checker {
constructor() {
this.url
this.logData = this.logData.bind(this);
this.getData = this.getData.bind(this);
}
async getData(url) {
const response = await got(url);
const data = await response.body;
return data;
}
async logData(first, second, threshold) {
let data = await this.getData(this.url)
console.log("received " + data.body);
}
}
I am trying to mock "getData" so I can write a unit test for "logData". Do I need to mock out the entire "got" module? Thanks.
If you change invoking got to got.get you should be able to have a working test like so:
const got = require('got');
const Checker = require('../index.js');
describe("some test", () => {
beforeEach(() => {
jest.spyOn(got, 'get').mockResolvedValue({ response: { body: { somekey: "somevalue" } } } );
});
it("works", async () => {
new Checker().getData();
expect(got.get).toBeCalledTimes(1);
})
})
One approach is to use dependency injection. Instead of calling 'got' directly, you can 'ask for it' in the class constructor and assign it to a private variable. Then, in the unit test, pass a mock version instead which will return what you want it to.
const got = require("got");
class Checker {
constructor(gotService) {
this.got = gotService;
this.logData = this.logData.bind(this);
this.getData = this.getData.bind(this);
}
async getData(url) {
const response = await this.got(url);
const data = await response.body;
return data;
}
async logData(first, second, threshold) {
let data = await this.getData(this.url)
console.log("received " + data.body);
}
}
//real code
const real = new Checker(got);
//unit testable code
const fakeGot = () => Promise.resolve(mockedData);
const fake = new Checker(fakeGot);
Here is what we are doing:
'Inject' got into the class.
In the class, call our injected version instead of directly calling the original version.
When it's time to unit test, pass a fake version which does what you want it to.
You can include this directly inside your test files. Then trigger the test that makes the Http request and this will be provided as the payload.
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { eth: 0.6, btc: 0.02, ada: 1 } }),
})
);
it('should return correct mock token values', async () => {
const addresses = ["mockA", "mockB", "mockC"];
const res = await getTokenData(addresses);
expect(res.data).toEqual({ eth: 0.6, btc: 0.02, ada: 1 });
});
I am implementing function monthlyRevenue.
Simply, it will return total monthly revenue,and it takes arguments of station array which will make revenues, month and year.
Problem
Inside of this function I have getStationPortion which will fetch the revenue portion of user's.
So I would like to make it return object like this.
stationsPortion = {station1 : 30, station2 : 20}
In the monthlyRevenue
const stationPortions = await getStationPortions(stations)
console.log("portion map", stationPortions //it will be shown very beginning with empty
getStationPortions
const getStationPortions = async (stations) => {
let stationPortions = {}
stations.map(async (value) => {
const doc = await fdb.collection('Stations').doc(value).get()
if (!doc.exists) {
console.log("NO DOC")
} else {
stationPortions[value] = doc.data().salesPortion
console.log(stationPortions) //it will be shown at the last.
}
})
return stationPortions
}
I thought that async function should wait for the result, but it does not.
I am kind of confusing if my understanding is wrong.
Thank you
(by the way, fdb is firebase admin(firestore)
Working code
const getStationPortions = async (stations) => {
let stationPortions = {}
await Promise.all(stations.map(async (value) => {
const doc = await fdb.collection('Stations').doc(value).get()
if (!doc.exists) {
console.log("NO DOC")
} else {
stationPortions[value] = doc.data().salesPortion
console.log(stationPortions)
}
}))
return stationPortions
}
module.exports = router;
i'm facing a issue with my unit test, stuck completely, the code is simple, please need to understand what's going on, my stub is never called, the set seems to be correct, here the code:
let strategy = fixtures.load('strategy')
chai.use(chaiAsPromised)
describe.only('Spawn Order Job', () => {
let getPositionsStub, createJobStub, daoStub,sandbox
beforeEach(()=>{
sandbox = sinon.createSandbox()
daoStub = sandbox.stub(dao, 'updateActiveOrders').resolves(true) //async
getPositionsStub = sandbox.stub(strategyModule, 'getPositions') //sync
createJobStub = sandbox.stub(helpers, 'createJob') //sync
createJobStub.returns(true)
getPositionsStub.resolves([{fake:'t'}, {fake:'t'}])
})
afterEach(()=>{
sandbox.restore()
})
//OK
it('Should failed with no param, type error context', ()=> {
const promise = spawnOrderJob()
expect(promise).to.be.rejectedWith(TypeError)
})
//OK
it('Should throw error timeout order', () => {
getPositionsStub.resolves([{fake:'t'}, {fake:'t'}])
strategy.lastDateOrder = new Date()
const ctx = { state: {strategy, dashboard, position:null}}
const action = {b: true, s: false}
const promise = spawnOrderJob(action, ctx)
expect(getPositionsStub.called).to.be.true
expect(daoStub.called).to.be.false
expect(createJobStub.called).to.be.false
expect(promise).to.be.rejectedWith(ORDER_ERROR, 'Timeout between order not expired.')
})
//KO stub never called
it.only('Should pass validation on buy', () => {
strategy.lastDateOrder = 0
const ctx = { state: {strategy, dashboard, position: null }}
const action = {b: true, s: false}
const promise = spawnOrderJob(action, ctx)
expect(promise).to.be.fulfilled
expect(getPositionsStub.called).to.be.true //ok
expect(createJobStub.called).to.be.true //never callled ????
expect(daoStub.called).to.be.true //never called ????
})
})
Want to understand what's going now there, the call are correct imo, running with mocha 5.2
Helpers.js : function is described as follow:
async function spawnOrderJob(action, ctx) {
try {
const { strategy, dashboard, position } = ctx.state
const {b, s} = action
//check in case strategy context
if (strategy) {
//pass validation buy contnext
if (b) {
//this stub is working
const positions = await strategyModule.getPositions(ctx)
const { maxPosition } = strategy.allocatedBTC
const { activeOrders, maxActiveOrders, timeBetweenOrder, lastDateOrder } = strategy
debug('Active orders:', strategy.activeOrders)
debug('Position:', positions.length)
if (activeOrders >= maxActiveOrders)
throw new ORDER_ERROR('Max active orders reach.')
if (positions.length + activeOrders >= maxPosition)
throw new ORDER_ERROR('Max positions reach.')
if (!timeoutExpired(lastDateOrder, timeBetweenOrder))
throw new ORDER_ERROR('Timeout between order not expired.')
//increment active orders counter
//stub fail, but not called at all
await dao.updateActiveOrders(strategy, true)
}
//Sell context
if (s) {
if (!position)
throw new ORDER_ERROR('No position to sell')
}
}
//stub fail, but called internally
return createJob(constants.DASHBOARD_CREATE_ORDER, {
orderType: b ? 'BUY' : 'SELL',
title: `Strategy create order ( ${ b ? 'BUY' : 'SELL'} )`,
strategy,
dashboard,
position
})
} catch (e) {
throw e
}
}
function createJob(name, data){
//shortcut queue.create (kue.js)
return queue.c(name,data)
}
module.exports = {
createJob,
spawnOrderJob
}
DAO
const updateActiveOrders = async (strategy, increment) => {
try {
const s = await model.findOne({_id: strategy._id})
if (!s) throw new Error('Strategy not found.')
s.activeOrders = increment ? s.activeOrders+1 :s.activeOrders-1
s.lastDateOrder = new Date()
return await s.save()
}catch(e){
throw e
}
}
module.exports = {updateActiveOrders}
I'm trying to get sinon.stub to work for async function. I have created promiseFunction.js:
let functionToBeStubbed = async function() {
return ("Text to be replaced by stub.");
};
let promiseFunction = async function() {
return(await functionToBeStubbed());
};
module.exports = {
promiseFunction: promiseFunction,
functionToBeStubbed: functionToBeStubbed
};
and test promiseFunction.spec.js:
let functionstobestested = require('./promiseFunction.js');
describe('Sinon Stub Test', function () {
var sandbox;
it('should return --Text to be replaced by stub.--', async function () {
let responsevalue = "The replaced text.";
sandbox = sinon.sandbox.create();
sandbox.stub(functionstobestested, 'functionToBeStubbed').resolves(responsevalue);
//sandbox.stub(functionstobestested, 'functionToBeStubbed').returns(responsevalue);
let result = "Empty";
console.log(`BEFORE: originaldata = ${result}, value = ${responsevalue}`);
result = await functionstobestested.promiseFunction();
console.log(`AFTER: originaldata = ${result}, value = ${responsevalue}`);
expect(result).to.equal(responsevalue);
sandbox.restore();
console.log("AFTER2: Return value after restoring stub: " + await functionstobestested.promiseFunction());
});
});
when running the test, I will get
test failure
If I modify export slightly, it still fails:
var functionsForTesting = {
promiseFunction: promiseFunction,
functionToBeStubbed: functionToBeStubbed
};
module.exports = functionsForTesting;
I do not understand why this test fails, as it should pass. If I change the way I export functions from promiseFunction.js - module, the test pass correctly. Revised promiseFunction.js:
const functionsForTesting = {
functionToBeStubbed: async function() {
return ("Text to be replaced by stub.");
},
promiseFunction: async function() {
return(await functionsForTesting.functionToBeStubbed());
};
module.exports = functionsForTesting;
Test pass
What's wrong in my original and modified way to export functions?