I'm expecting both of these toEqual() tests to pass. The first one passes, but the second one doesn't. Why?
test('something', () => {
const m = Map({
a: 1,
b: 2,
})
expect(m.valueSeq()).toEqual(m.valueSeq());
expect(m.valueSeq().filter(_ => true)).toEqual(m.valueSeq());
});
This line:
expect(m.valueSeq().filter(_ => true)).toEqual(m.valueSeq());
...calls toEqual which calls equal passing it iterableEquality as a custom matcher:
const pass = equals(received, expected, [iterableEquality]);
iterableEquality ends up returning false on this line:
if (a.constructor !== b.constructor) {
return false;
}
...because the constructors aren't equal:
const { Map } = require('immutable');
test('something', () => {
const m = Map({
a: 1,
b: 2,
})
const seq = m.valueSeq();
const filter = m.valueSeq().filter(_ => true);
console.log(seq.constructor); // [Function: ToIndexedSequence]
console.log(filter.constructor); // [Function: IndexedCollection]
expect(seq.constructor).not.toBe(filter.constructor); // Success! (different constructors)
});
Related
I'm trying to spyOn exported function that another function in the same file calls.
I can't seem to get Jest to use the spy at all.
Tried with a mixture of mockImplementation and mockReturnValue.
utils.ts:
export const func1 = (x: number) => {
const y = x + 1
return y
}
export const func2 = (a: number) => {
const b = a + 2
return b
}
export const func3 = (x: number, y: number) => {
const foo = func1(x)
const bar = func2(y)
return foo + bar
}
utils.test.ts:
import * as sut from './utils'
describe('test', () => {
test('func3 spys', () => {
let func1Spy: jest.SpyInstance
let func2Spy: jest.SpyInstance
func1Spy = jest.spyOn(sut, 'func1').mockImplementation(() => 8)
func2Spy = jest.spyOn(sut, 'func2').mockImplementation(() => 10)
const result = sut.func3(1, 2)
expect(func1Spy).toHaveBeenCalled()
expect(func2Spy).toHaveBeenCalled()
expect(result).toBe(6)
})
})
Expected behavior: Jest sees that the spy'd function has been called
Actual output:
Expected number of calls: >= 1
Received number of calls: 0
Commenting out the spy call assertions give me a passing test so the function is running correctly. Just without the spys
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'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 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
I am trying to implement a simple search scheme to search for values in one observable from another observable. The buildLookup function below builds a lookup table using values from an observable:
// Build lookup table from an observable.
// Returns a promise
function buildLookup(obs, keyName, valName) {
const map = new Map();
obs.subscribe((obj) => map.set(obj[keyName], obj[valName]));
// use concat to force wait until `obs` is complete
return obs.concat(Observable.from([map])).toPromise();
}
Then I have another function this uses the result of this function (a promise):
// Lookup in a previously built lookup table.
function lookup(source, prom, keyName, fieldName) {
return source.map((obj) => {
const prom2 = prom.then((map) => {
return lodash.assign({}, obj, { [fieldName]: map.get(String(obj[keyName])) });
});
return Observable.fromPromise(prom2);
})
.flatMap((x) => x);
}
For some reason, this implementation does not work, and every other lookup seems to fail. Could you someone guide me on:
what is wrong with this code, and
whether there is a better way to implement something like this?
Thanks a bunch in advance for your help!
I am attaching my test code below:
"use strict";
const lodash = require("lodash");
const rxjs = require("rxjs");
const chai = require("chai");
const Observable = rxjs.Observable;
const assert = chai.assert;
const assign = lodash.assign;
describe("search", () => {
it("simple search", (done) => {
let nextId = 1, nextId2 = 1;
const sourceObs = Observable.interval(5).take(5).map((i) => {
const id = nextId++;
return { id: `${id}` };
});
const searchableObs = Observable.interval(5).take(5).map((i) => {
const id = nextId2++;
return Observable.from([
{ id: `${id}`, code: "square", val: id * id },
]);
}).flatMap((x) => x);
const results = [];
const verifyNext = (x) => {
assert.isDefined(x);
results.push(x);
};
const verifyErr = (err) => done(err);
const verifyComplete = () => {
assert.equal(results.length, 5);
try {
results.forEach((r) => {
console.log(r);
// assert.equal(r.val, r.id*r.id); <== *** fails ***
});
} catch (err) {
done(err);
}
done();
};
// main
const lookupTbl = buildLookup(searchableObs, "id", "val"); // promise that returns a map
lookup(sourceObs, lookupTbl, "id", "val")
.subscribe(verifyNext, verifyErr, verifyComplete)
;
});
});
// output
// { id: '1', val: 1 }
// { id: '2', val: undefined }
// { id: '3', val: 9 }
// { id: '4', val: undefined }
// { id: '5', val: 25 }
So, a bunch of things to address here.
The main issue is that you are doing side-effects in your sourceObs and searchableObs observables, and it is not published, so the side-effects happen multiple times because you subscribe multiple times, giving you a wrong map entirely. For instance, I get maps like:
{"1" => 1, "4" => 16, "7" => 49, "12" => 144}
But you are doing something so trivial that you should really not use mutable variables.
To solve this, here is how you can create the proper observables:
const sourceObs = Rx.Observable.range(1, 5).map(i => ({ id: `${i}` }));
const searchableObs = Rx.Observable.range(1, 5).map(i =>
({ id: `${i}`, code: "square", val: i * i })
);
There is no reason to use a variable since range returns the numbers 1, 2, ...
And your use of o.map(_ => Rx.Observable.from(...)).concatMap(e => e) is really just the same as o...
While I'm here, this is a simplified version of your correct but clumsy functions:
// so buildLookup just needs to return a map once it's finished populating it
function buildLookup(obs, keyName, valName) {
// following your style here, though this could be done using `scan`
const map = new Map();
obs.subscribe((obj) => map.set(obj[keyName], obj[valName]));
// instead of your promise, I just wait for `obs` to complete and return `map` as an observable element
return obs.ignoreElements().concat(Rx.Observable.of(map));
}
// and lookup just needs to wait for the map, and then populate fields in the object
function lookup(source, prom, keyName, fieldName) {
return prom
.concatMap(map => source.map(obj => ({ obj: obj, map: map })))
.map(({ obj, map }) => lodash.assign({}, obj, { [fieldName]: map.get(String(obj[keyName])) }))
;
}
This should work for you.