How to wait for result of service get? - jasmine

I'm pretty sure the get function of the person service I use below returns a function, but my test doesn't seem to execute either branch, so it times out. Does that mean that get does not return a promise?
describe('person service', function () {
beforeEach(module('tournament'));
var person;
beforeEach(inject(function (_personService_) {
person = _personService_;
}));
it('should get person data', (done) => {
var expectedPerson = [
{ id: 2, last: 'Steigerwald', first: 'Michael' },
];
person.get(2).then(
(result) => {
expect(result).toEqual(expectedPerson);
done();
},
(reason) => {
console.log(reason.message);
done();
}
);
});
});

Related

Moxios Requests State Not Cleared In Between Tests

My specs are behaving weirdly in that when I run the tests alone, they pass. However, when I run the test suite all together, the failure tests still continue to use the success axios mock instead of using the correct failing http axios mock. This results in my tests failing. Am I missing something for isolating the 2 mocks from each other in the different portions of code?
jobactions.js
export const loadUnassignedJobs = (job_type) => {
if (!['unscheduled', 'overdue'].includes(job_type)) {
throw 'Job Type must be "unscheduled" or "overdue".';
}
return (dispatch) => {
dispatch({type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED, job_type });
return axios.get(defaults.baseapi_uri + 'jobs/' + job_type)
.then(function (response) {
dispatch(updateUnassignedJobs(response.data.jobs));
// handle success
})
.catch(function (error) {
// handle error
dispatch({ type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE, error });
})
.then(function () {
// always executed
});
}
};
export const updateUnassignedJobs = (unassigned_jobs) => {
let unassigned_job_ids = [];
let jobs = {};
for (let job of unassigned_jobs) {
unassigned_job_ids.push(job.id);
jobs[job.id]=job;
}
return({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs,
unassigned_job_ids,
});
};
spec.js
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as jobActions from "../../../app/javascript/actions/JobActions"
import { findAction } from '../support/redux_store'
import * as JobActionTypes from '../../../app/javascript/constants/JobActionTypes'
import fixtures_jobs_unscheduled_success from '../fixtures/jobs_unscheduled_success'
import moxios from "moxios";
export const mockStore = configureMockStore([thunk]);
let store;
describe ('loadUnassignedJobs', () => {
context('when bad parameters are passed', async () => {
it('will raise an error', () => {
const store = mockStore();
expect(() => {
store.dispatch(jobActions.loadUnassignedJobs('wrong_type'));
}).to.throw('Job Type must be "unscheduled" or "overdue".');
});
});
context('when unscheduled is passed', () => {
beforeEach(() => {
moxios.install();
console.log("before each called");
console.log(moxios.requests);
store = mockStore();
store.clearActions();
});
afterEach(() => {
console.log("after each called");
console.log(moxios.requests);
moxios.uninstall();
});
context('on success', () => {
beforeEach(() => {
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: fixtures_jobs_unscheduled_success
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_STARTED', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED)).to.be.eql({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED,
job_type: 'unscheduled'
});
});
});
it('dispatches updateUnassignedJobs()', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store,JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.eql(jobActions.updateUnassignedJobs(fixtures_jobs_unscheduled_success.jobs))
});
});
});
context('on error', () => {
beforeEach(() => {
//console.log("before each on error called");
//console.log(moxios.requests);
moxios.wait(() => {
console.log('after waiting for moxios..')
console.log(moxios.requests);
let request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: { error: 'internal server error' }
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_FAILURE', (done) => {
console.log(moxios.requests);
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
console.log(moxios.requests);
console.log(store.getActions());
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE)).to.include({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE
});
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE).error).to.include({
message: 'Request failed with status code 500'
});
done();
});
});
it('does not dispatch LOAD_UNASSIGNED_JOBS_SUCCESS', (done) => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.undefined;
done();
});
});
})
});
});
describe('updateUnassignedJobs', () => {
it('assigns jobs to hash and creates an unassigned_job_ids array', () => {
expect(jobActions.updateUnassignedJobs([ { id: 1, step_status: 'all_complete' }, { id: 2, step_status: 'not_started' } ])).to.be.eql(
{
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs: { 1: { id: 1, step_status: 'all_complete' }, 2: { id: 2, step_status: 'not_started' } },
unassigned_job_ids: [ 1,2 ]
}
)
});
});
Found the issue!
The it() blocks for the success case were not using the done callback causing the afterEach() moxios.uninstall() to be called prematurely and not resetting the requests after the call was complete. Fixing this, and now all the tests pass.

Testing if method with Promise was called (Jest)

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

Mocha not registering 'it' blocks inside promise list

I'm trying to write a test that will run a GET over all items. To do this, I get that list in the before block, then I want to have an it block for each item. I am trying to do this by putting the it block inside itemList.forEach. However, I suspect that the problem here is that the blocks never get registered for the test. How can I run this test as desired?
let token;
let itemList;
describe('GET items/:itemId with Admin', async () => {
before(async () => {
// NOTE: item.find({}) returns a promise of a list of all items
itemList = await item.find({});
console.log(item[0]._id) // this logs correctly!
const res = await userLogin(admin);
token = res.body.accessToken.toString();
});
it('registers initial it test', () => {
// This test passes and logs the statement
console.log('first test registered')
console.log(itemList.length) // successfully logs non-zero value
})
await itemList.forEach(async (item) => {
it('respond with json with a item', () => {
const itemId = item._id;
return getItem(itemId, token)
.then((response) => {
assert.property(response.body, '_id');
});
});
});
});
Afaik the before setup runs before every it test. It doesn't run immediately, and definitely does not wait for anything until you try to iterate your itemList. I think you will need to do either
describe('GET items/:itemId with Admin', async () => {
let token;
before(async() => {
const res = await userLogin(admin);
token = res.body.accessToken.toString();
});
// a list of all items for which tests should be created
const itemList = await item.find({});
console.log(itemList.length) // successfully logs non-zero value
for (const item of itemList) {
it('responds with json for item '+item, () => {
const itemId = item._id;
return getItem(itemId, token).then((response) => {
assert.property(response.body, '_id');
});
}
});
or
describe('GET items/:itemId with Admin', () => {
let itemList;
let token;
before(async() => {
[itemList, token] = await Promise.all([
item.find({}),
userLogin(admin).then(res => res.body.accessToken.toString())
]);
});
it('responds with json for every item', () => {
return Promise.all(itemList.map(item => {
const itemId = item._id;
return getItem(itemId, token)
.then((response) => {
assert.property(response.body, '_id');
});
});
}));
});
});
This is the solution I ended up with. I ended up putting a new describe block in the before block. The before block results the promise that gives the list of items. There is an it block in the top level so that mocha registers the test in the first place.
describe('GET items/:itemId with Admin', async () => {
before((done) => {
Item.find({}).then(async (itemList) => {
// create the admin user to get the items with
await createUsers([admin]);
const res = await userLogin(admin);
const token = res.body.accessToken.toString();
itemList.forEach((item, index) => {
const itemId = item._id;
describe(`get item number ${index}: _id: ${itemId}`, () => {
it('responds with item id', () =>
getItem(item, token)
.expect(200)
.then((response) => {
assert.notProperty(response.body, 'error');
assert.property(response.body, '_id');
assert.equal(response.body._id, itemId);
}));
});
});
done();
});
});
// If there is no it block here, it will not run the before block!
it(`register the initial it`, () => {
assert.equal('regression test!', 'regression test!');
});
});

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 :)

Failing test with bookshelf.js and bookshelf-validate

I am new to node and I am just attempting at writing a simple backend blog API. I am using bookshelf.js as the ORM and I am attempting to use bookshelf-validate in order to enforce requirements on an Article model that I made. The validations I have included with the Article model are merely the isRequired validations on all fields (fields being title, author, and body). One of my tests creates a new article with all the fields defined and the test is failing. Here is my code,
//here is the bookshelf model
const Bookshelf = require('../config/bookshelf.config');
const Article = Bookshelf.Model.extend({
tableName: 'articles',
hasTimestamps: true,
validations: {
title: {
isRequired: true
},
author: {
isRequired: true
},
body: {
isRequired: true
}
}
});
module.exports = Bookshelf.model('Article', Article);
//test file below
process.env.NODE_ENV = 'test';
const chaiAsPromised = require('chai-as-promised');
const { expect, assert } = require('chai').use(chaiAsPromised);
const knex = require('knex')(require('../knexfile')[process.env.NODE_ENV]);
const Article = require('../models/article');
describe('Articles', function () {
beforeEach(function () {
return knex.migrate.rollback()
.then(function () {
return knex.migrate.latest();
});
});
after(function () {
return knex.migrate.rollback();
});
describe('test db', function () {
it('should not have any models at start of test suite', function () {
Article.forge().fetch().then(function (results) {
expect(results).to.equal(null);
});
});
it('should save a model to the db', function () {
const article = new Article({
title: 'first blog',
author: 'john doe',
body: 'blah blah'
}).save();
return expect(article).to.be.fulfilled;
});
});
});
Here is the gist as well https://gist.github.com/Euklidian-Space/bf10fd1a72bec9190867854d1ea309d9
Thanks in advance.
Your should save a model to the db test is not taking asynchronicity into account. It may save the entry but the expect() call may come too early to get the fulfilled promise.
So replace
it('should save a model to the db', function () {
const article = new Article({
title: 'first blog',
author: 'john doe',
body: 'blah blah'
}).save();
return expect(article).to.be.fulfilled;
});
by something like
it('should save a model to the db', function (done) {
const article = new Article({
title: 'first blog',
author: 'john doe',
body: 'blah blah'
})
.save()
.then(function () { done() })
.catch(function (err) { done(err) });
});

Resources