Testing if method with Promise was called (Jest) - promise

I have an initializer method calling another method that returns a promise, like:
initStuffAfterLoad() {
const _this = this;
const theInterval = window.setInterval(function() {
if (thing) {
window.clearInterval(theInterval);
_this.getBanana()
.then(response => {
_this.getApple(response, _this);
});
}
}, 100);
}
and am needing to test whether getBanana was called (jest/sinon). So far I have:
test('init function calls getBanana', () => {
let thing = true
const getBananaSpy = sinon.spy();
sinon.stub(TheClass.prototype, 'getBanana').callsFake(getBananaSpy).resolves();
jest.useFakeTimers();
TheClass.prototype.initStuffAfterLoad();
jest.runOnlylPendingTimers();
expect(getBananaSpy.called).toBeTruthy();
TheClass.prototype.getBanana.restore();
});
However it still receives false at the assertion. I figure I'm not handling the Promise part correctly - what is the best practice way to do this?

I am not familiar with sinon, but here is a way to achieve your need with pure jest (even better it also checks that getApple is called when getBanana reseolves :))
jest.useFakeTimers()
const _this = {
getBanana: () => {},
getApple: () => {}
}
const initStuffAfterLoad = () => {
const theInterval = window.setInterval(function() {
window.clearInterval(theInterval);
_this.getBanana().then(response => {
_this.getApple(response, _this)
});
}, 100);
}
test('', () => {
let result
_this.getBanana = jest.fn(() => {
result = new Promise( resolve => { resolve() } )
return result
})
_this.getApple = jest.fn()
initStuffAfterLoad()
jest.runAllTimers()
expect(_this.getBanana.mock.calls.length).toBe(1)
return result.then(() => {
expect(_this.getApple.mock.calls.length).toBe(1)
})
})
code tested :)
PASS test\temp.test.js √ (25ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.489s

Related

How to wait for request to be finished with axios-mock-adapter like it's possible with moxios?

I try to test a rendering of some content after fetching it from server.
I use Vue Test Utils but this is irrelevant.
In the created hook of the component the ajax call is made with axios. I register the axios-mock-adapter response and 'render' the component, the call is made and everything works fine but i have to use the moxios lib only to wait for request to be finished.
it('displays metrics', (done) => {
this.mock.onGet('/pl/metrics').reply((config) => {
let value = 0
if (config.params.start == '2020-01-26') {
value = 80
}
if (config.params.start == '2020-01-28') {
value = 100
}
return [200, {
metrics: [
{
key: "i18n-key",
type: "count",
value: value
}
]
}]
})
.onAny().reply(404)
let wrapper = mount(Dashboard)
moxios.wait(function() {
let text = wrapper.text()
expect(text).toContain('80')
expect(text).toContain('100')
expect(text).toContain('+20')
done()
})
})
Is it possible to get rid of moxios and achieve the same with axios-mock-adapter only?
Yes, you can implement your own flushPromises method with async/ await:
const flushPromises = () => new Promise(resolve => setTimeout(resolve))
it('displays metrics', async () => {
this.mock.onGet('/pl/metrics').reply((config) => {
// ..
}).onAny().reply(404)
let wrapper = mount(Dashboard)
await flushPromises()
expect(text).toContain('80')
})
Or use done and setTimeout:
it('displays metrics', (done) => {
this.mock.onGet('/pl/metrics').reply((config) => {
// ..
}).onAny().reply(404)
let wrapper = mount(Dashboard)
setTimeout(() => {
expect(text).toContain('80')
done()
})
})
moxiois.wait simply schedules a callback with setTimeout. This works because a task scheduled by setTimeout always runs after the microtask queue, like promise callbacks, is emptied.

how to cancel promises with bluebird

I think I misunderstand how promise cancellation with bluebird works. I wrote a test that demonstrates this. How do I make it green? Thanks:
describe('cancellation tests', () => {
function fakeFetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
}
function awaitAndAddOne(p1) {
return p1.then(res => res + 1);
}
it('`cancel` cancels promises higher up the chain', () => {
const p1 = fakeFetch();
const p2 = awaitAndAddOne(p1);
expect(p2.isCancellable()).toBeTruthy();
p2.cancel();
return p2
.then(() => {
console.error('then');
})
.catch(err => {
console.error(err);
})
.finally(() => {
expect(p2.isCancelled()).toBeTruthy(); // Expected value to be truthy, instead received false
expect(p1.isCancelled()).toBeTruthy();
});
});
});
From here:
The cancellation feature is by default turned off, you can enable it using Promise.config.
Seems like you didn't enable the cancellation flag on the Promise itself:
Promise.config({
cancellation: true
});
describe(...
#Karen if correct. But the issue is that your test is also a bit wrong
If you look at the isCancellable method
Promise.prototype.isCancellable = function() {
return this.isPending() && !this.isCancelled();
};
This is just checking if the promise is pending and is not already cancelled. This doesn't mean then cancellation is enabled.
http://bluebirdjs.com/docs/api/cancellation.html
If you see the above url, it quotes below
The cancellation feature is by default turned off, you can enable it using Promise.config.
And if you look at the cancel method
Promise.prototype["break"] = Promise.prototype.cancel = function() {
if (!debug.cancellation()) return this._warn("cancellation is disabled");
Now if I update your test correct like below
var Promise = require("bluebird");
var expect = require("expect");
describe('cancellation tests', () => {
function fakeFetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
}
function awaitAndAddOne(p1) {
return p1.then(res => res + 1);
}
it('`cancel` cancels promises higher up the chain', () => {
const p1 = fakeFetch();
const p2 = awaitAndAddOne(p1);
value = p2.isCancellable();
expect(p2.isCancellable()).toBeTruthy();
p2.cancel();
expect(p2.isCancelled()).toBeTruthy(); // Expected value to be truthy, instead received false
expect(p1.isCancelled()).toBeTruthy();
});
});
You can see that cancellation is not enable and it executes the warning code
The execution results fail as expected
spec.js:46
cancellation tests
spec.js:46
1) `cancel` cancels promises higher up the chain
spec.js:78
0 passing (3m)
base.js:354
1 failing
base.js:370
1) cancellation tests
base.js:257
`cancel` cancels promises higher up the chain:
Error: expect(received).toBeTruthy()
Expected value to be truthy, instead received
false
at Context.it (test/index.test.js:37:36)
Now if you update the code to enable cancellation
var Promise = require("bluebird");
var expect = require("expect");
Promise.config({
cancellation: true
});
describe('cancellation tests', () => {
function fakeFetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
}
function awaitAndAddOne(p1) {
return p1.then(res => res + 1);
}
it('`cancel` cancels promises higher up the chain', () => {
const p1 = fakeFetch();
const p2 = awaitAndAddOne(p1);
value = p2.isCancellable();
expect(p2.isCancellable()).toBeTruthy();
p2.cancel();
expect(p2.isCancelled()).toBeTruthy(); // Expected value to be truthy, instead received false
expect(p1.isCancelled()).toBeTruthy();
});
});
It works!

chai-as-promised should.eventually.equal not passing

I am trying to write a minimum working example of chai-as-promised in order to understand how it is working when testing functions that return a promise.
I have the following function:
simple.test = async (input) => {
return input;
};
and the following test function:
chai.use(sinonChai);
chai.use(chaiAsPromised);
const { expect } = chai;
const should = chai.should();
describe('#Test', () => {
it('test', () => {
expect(simple.test(1)).should.eventually.equal(1);
});
});
However, testing this results in the test not passing, but in a very long error, which is pasted here: https://pastebin.com/fppecStx
Question: Is there something wrong about the code, or what seems to be the problem here?
First: Your mixing expect and should. If you want to use should for assertion, you don't need expect.
Second: To tell mocha that a test is async you have to either call done, return a Promise or use async/await.
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const sinonChai = require('sinon-chai');
const should = chai.should();
chai.use(sinonChai);
chai.use(chaiAsPromised);
// Function to test
const simple = {
test: async (input) => {
return input;
}
}
// Test
describe('#Test', () => {
it('test', () => {
return simple.test(1).should.eventually.equal(1);
});
});

How can I test Observable.ajax (redux-observable)?

I have been playing with rxjs and redux-observable for the last few days and have been struggle to find a way to a test for Observable.ajax. I have the following epic which create a request to https://jsonplaceholder.typicode.com/,
export function testApiEpic (action$) {
return action$.ofType(REQUEST)
.switchMap(action =>
Observable.ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
)
}
where,
export const REQUEST = 'my-app/testApi/REQUEST'
export const SUCCESS = 'my-app/testApi/SUCCESS'
export const FAILURE = 'my-app/testApi/FAILURE'
export const CLEAR = 'my-app/testApi/CLEAR'
export function requestTestApi () {
return { type: REQUEST }
}
export function successTestApi (response) {
return { type: SUCCESS, response }
}
export function failureTestApi (error) {
return { type: FAILURE, error }
}
export function clearTestApi () {
return { type: CLEAR }
}
The code works fine when runs in browser but not when testing with Jest.
I have try,
1) Create a test based on https://redux-observable.js.org/docs/recipes/WritingTests.html. The store.getActions() returns only { type: REQUEST }.
const epicMiddleware = createEpicMiddleware(testApiEpic)
const mockStore = configureMockStore([epicMiddleware])
describe.only('fetchUserEpic', () => {
let store
beforeEach(() => {
store = mockStore()
})
afterEach(() => {
epicMiddleware.replaceEpic(testApiEpic)
})
it('returns a response, () => {
store.dispatch({ type: REQUEST })
expect(store.getActions()).toEqual([
{ type: REQUEST },
{ type: SUCCESS, response }
])
})
})
2) Create a test based on Redux-observable: failed jest test for epic. It returns with
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('returns a response', (done) => {
const action$ = ActionsObservable.of({ type: REQUEST })
const store = { getState: () => {} }
testApiEpic(action$, store)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: SUCCESS, response }
])
done()
})
})
Can someone point me out what is the correct way to test Observable.ajax ?
I would follow the second example, from StackOverflow. To make it work you'll need to make some minor adjustments. Instead of importing Observable.ajax in your epic file and using that reference directly, you need to use some form of dependency injection. One way is to provide it to the middleware when you create it.
import { ajax } from 'rxjs/observable/dom/ajax';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { ajax }
});
The object we passed as dependencies will be give to all epics as the third argument
export function testApiEpic (action$, store, { ajax }) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Alternatively, you could not use the dependencies option of the middleware and instead just use default parameters:
export function testApiEpic (action$, store, ajax = Observable.ajax) {
return action$.ofType(REQUEST)
.switchMap(action =>
ajax({ url, method })
.map(data => successTestApi(data.response))
.catch(error => failureTestApi(error))
.takeUntil(action$.ofType(CLEAR))
);
}
Either one you choose, when we test the epic we can now call it directly and provide our own mock for it. Here are examples for success/error/cancel paths These are untested and might have issues, but should give you the general idea
it('handles success path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
successTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('handles error path', (done) => {
const action$ = ActionsObservable.of(requestTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.throw({ url, method })
};
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe(actions => {
expect(actions).to.deep.equal([
failureTestApi({ url: '/whatever-it-is', method: 'WHATEVERITIS' })
])
done();
});
});
it('supports cancellation', (done) => {
const action$ = ActionsObservable.of(requestTestApi(), clearTestApi())
const store = null; // not used by epic
const dependencies = {
ajax: (url, method) => Observable.of({ url, method }).delay(100)
};
const onNext = chai.spy();
testApiEpic(action$, store, dependencies)
.toArray()
.subscribe({
next: onNext,
complete: () => {
onNext.should.not.have.been.called();
done();
}
});
});
For the first way:
First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)

Wait for n executions of a method and then continue after complete

I have a function that returns promise:
Setup.zoomIn() : Promise<void> {...}
I would like to use rxjs to invoke that function 5 times with delay of 1 second between each, like this:
let obs = Observable.create(observer => {
let count = 0;
setTimeout(() => {
Setup.zoomIn();
count++;
observer.next();
}, 1000);
if (count === 5) {observer.complete();}
};
obs.subscribe(() =>
console.log('zoomed out');
)};
Only and only when that is executed I would like to continue with execution to perform further steps:
obs.toPromise.then(() => {
// do some stuff here but only after zoom has been invoked 5 times
})
Create a list of observables for zoomIns functions and concat them with another Observable.
function zoomIn(i) {
return new Promise(res => {
setTimeout(()=>res(i), 1000);
});
};
function anotherPromise() {
return Rx.Observable.defer(()=> {
return new Promise(res => {
setTimeout(()=>res('anotherPromise'), 3000);
});
});
}
const zoonInList = Array(5).fill(0).map((x, i)=>i).map(i=>
Rx.Observable.defer(()=> {
return zoomIn(i);
})
);
Rx.Observable.concat(...zoonInList, anotherPromise())
.subscribe(x=>console.log(x))

Resources