How to run Promise test in Jasmine - jasmine

I'm trying to test a promise in a separate library I injected to my app.
function myFunc(input) {
return new Promise(function(resolve, reject) {
···
resolve(value); // success
···
reject(error); // failure
});
};
This is my function that returns a Promise.
I would seriously love to run the test in jasmine like this
describe('Service: myService', function () {
var $log;
beforeEach(inject(function (_$log_) {
$log = _$log_;
}));
it('should get results', function () {
$log.log("start test");
var self = this;
myFunc(input).then(function(response) {
$log.log("success");
expect(response).toBe("response");
done();
}).catch(function(error) {
$log.log("fail");
self.fail(error);
done();
});
$log.log("end test");
});
});
My test passes(not expected.) and the only thing in my log is [start test] and [end test] as if the promise is totally ignored.
Since I'm not using $q for the promise, most jasmine tips angular doesn't seem to be helpful.
Any ideas on how to get into that 'then'?
Thanks

I could be wrong here, but this is most likely because you are attempting to test an asynchronous process here. So essentially what is happening, is that you call the function, but the test continues running and finishes before the promise ever returns, which is why the test succeeds.
One way to work around this (this is more like a hack, I'm sure there is a better way to do this, and if I find it, I will edit this post) is to add this bit of code at the end of your test:
describe("baseline test",function(){
it("baseline",function(done){
setTimeout(function(){
expect(1).toEqual(1);
done();
},1000);
});
});
Essentially what this snippet does is just set a timeout that waits for 1 second for any asynchronous calls to call back. If 1 second isn't enough, you can always increase that 1000 (which is in milliseconds). If this doesn't work, maybe look into adding another test suite that won't complete until the promises return.

TypeScipt code (Angular 8) to be tested
import { Injectable } from '#angular/core';
import { KeycloakService } from 'keycloak-angular';
import { environment } from 'environments/environment';
import { LoggedInUserHelperService } from 'shared/helper/logged-in-user-helper.service';
#Injectable({
providedIn: 'root'
})
export class AppInitializationService {
constructor(public keycloakService: KeycloakService) {
}
initApplication(): Promise<any> {
return new Promise<any>(
async (resolve: any, reject: any): Promise<any> => {
await this.initKeycloak()
.then(() => keyCloakInitialized = true)
.catch((error: Error) => {
console.error(`Couldn\'t initialize Keycloak Service. (Error: ${error})`);
reject(error);
return;
});
resolve();
}
);
}
private async initKeycloak(): Promise<any> {
return this.keycloakService.init({
config: environment.keycloak,
initOptions: {
onLoad: 'login-required',
checkLoginIframe: false,
promiseType: 'legacy'
},
enableBearerInterceptor: true,
bearerExcludedUrls: ['/assets']
});
}
}
Tests
import { TestBed } from '#angular/core/testing';
import { AppInitializationService } from 'app/app-initialization.service';
import { KeycloakService } from 'keycloak-angular';
describe('AppInitializationService', () => {
let testObj: AppInitializationService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
KeycloakService
]
});
testObj = TestBed.get(AppInitializationService);
});
it('should call keycloak service init', async () => {
const spy = spyOn(testObj.keycloakService, 'init').and.returnValue(Promise.resolve(true));
await testObj.initApplication();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({
config: environment.keycloak,
initOptions: {
onLoad: 'login-required',
checkLoginIframe: false,
promiseType: 'legacy'
},
enableBearerInterceptor: true,
bearerExcludedUrls: ['/assets']
});
});
it('should log error on failed keycloak initialization', async () => {
const errorMsg = 'error-msg';
const spy1 = spyOn(testObj.keycloakService, 'init').and.callFake(
() => Promise.reject(errorMsg));
const spy2 = spyOn(console, 'error');
await testObj.initApplication().catch(() => { return; });
expect(spy1).toHaveBeenCalledTimes(1);
expect(spy2).toHaveBeenCalledTimes(1);
expect(spy2).toHaveBeenCalledWith(`Couldn\'t initialize Keycloak Service. (Error: ${errorMsg})`);
});
});

Related

Service Worker registers but doesn't cache

I'm new to service workers and I'm running into an issue with my implementation. My goal is to create a runtime cache for images and videos. I've looked at the workbox implementation but it hasn't worked for me. I see that my service worker successfully registers at the top-level scope of my app but for some reason, it seems like some of the code in my service worker file doesn't get executed. The main issue is that the event listeners from my service worker don't seem to get called (including registerRoute), and therefore, the Cache doesn't ever get created.
I'm not sure if this is related to the issue I'm having but when I look at the console messages, it seems like the code from sw.js may be run prior to the service worker registration:
console messages
I've been stuck on this problem for a while so I would appreciate some help if anyone has run into this issue, thanks!
// main.js (in a Vue 2 app)
if (process.env.NODE_ENV === "production") {
window.addEventListener("load", () => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(`/sw.js`)
.then(() => {
console.log("Service worker registered!");
navigator.serviceWorker.ready.then((registration) => {
registration.update();
console.log('Service Worker: ready');
});
})
.catch((error) => {
console.warn("Error registering service worker:");
console.warn(error);
});
}
});
}
// sw.js
import { registerRoute } from "workbox-routing";
import { CacheFirst } from "workbox-strategies";
import { CacheableResponsePlugin } from "workbox-cacheable-response";
import { RangeRequestsPlugin } from "workbox-range-requests";
import { clientsClaim } from "workbox-core";
const CACHE_PREFIX = "background-slideshow-cache";
const CACHE_VERSION = "v1";
const CACHE_RUNTIME = "runtime";
const BACKGROUND_SLIDESHOW_CACHE = `${CACHE_PREFIX}-${CACHE_RUNTIME}-${CACHE_VERSION}`;
clientsClaim();
const addToCache = async (url) => {
const cache = await caches.open(BACKGROUND_SLIDESHOW_CACHE);
if (!(await cache.match(url))) {
await cache.add(url);
}
};
const cacheFirstStrategy = new CacheFirst({
cacheName: BACKGROUND_SLIDESHOW_CACHE,
plugins: [
new CacheableResponsePlugin({
statuses: [200],
}),
new RangeRequestsPlugin(),
],
});
self.addEventListener("message", (event) => {
if (event.data && event.data.message) {
if (event.data.message === "SKIP_WAITING") {
self.skipWaiting();
}
}
});
self.addEventListener("install", (event) => {
console.log('Service worker: fetch event', event);
})
console.log("Service Worker: in file");
registerRoute(
({ request }) => {
const { destination } = request;
console.log("Service Worker:", "request", request);
return destination === "video" || destination === "image";
},
({ event, request }) => {
// console.log("Service Worker: in the 2nd param", event, request);
event.respondWith(async () => {
await addToCache(request.url);
return cacheFirstStrategy.handle({ request });
});
}
);
After many hours of debugging, I realized that the minification of sw.js at build time was the reason this code wasn't able to execute. I decided to use uglifyjs-webpack-plugin in my webpack config and this solved the issue!

SequelizeDatabaseError: could not serialize access due to concurrent update

In Mocha test beforeEach hook, I am trying to destroy all table records.
import { db } from '../src/db/models';
export const truncateTable = () => {
const promises = Object.keys(db).map(key => {
if (key !== 'Sequelize' && key !== 'sequelize') {
console.log(key);
return db[key].destroy({ where: {} });
}
});
return Promise.all(promises);
};
Then in the test, I am doing this:
describe.only('application mutations', () => {
beforeEach(() => truncateTable());
...
The error I am getting:
SequelizeDatabaseError: could not serialize access due to concurrent
update
TL/DR: in your tests, if you want a quick way to delete models and reset your DB, use sync.
describe.only('application mutations', () => {
beforeEach(async () => {
await db.sync({force: true})
});
}
If you want to individually destroy your models, you must properly await for your promise to finish before initiating a new one. Currently, your promises are being initiated all at once, hence the Sequelize error.
export const truncateTable = async () => {
const promises = Object.keys(db).map(key => {
if (key !== 'Sequelize' && key !== 'sequelize') {
await db[key].destroy({ where: {} });
}
});
};
// in your test file
describe.only('application mutations', () => {
beforeEach(async () => {
await truncateTable();
});
})

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.

How to spyOn a function which has an Promise inside and don't return the result but handles the response itself?

How can I spyOn a method called placed in the object?
// Game.js
export default {
mine: null,
handle: function(me) {
console.log(" FOOOOO " + me)
},
setSource: function() {
this.mine.getSource().then((response) => {
const {source} = response
this.handle(source)
})
}
}
Here i try to spy:
// GameSpec.js
import Game from '../../lib/jasmine_examples/Game'
Game.mine = {}
describe("Game", function() {
it("should set source and handle it", function() {
Game.mine.getSource = () => {
return new Promise((resolve)=>{
resolve( {
source : 'BAAAAR'
})
})
}
spyOn(Game, 'handle').and.callThrough()
Game.setSource()
expect(Game.handle).toHaveBeenCalled()
});
});
In the output you can see the function "handle" was called:
Started
F FOOOOO BAAAAR
Failures:
1) Game should set source and handle it
Message:
Expected spy handle to have been called.
Stack:
Error: Expected spy handle to have been called.
at <Jasmine>
at UserContext.<anonymous> (/Users/silverbook/Sites/zTest/jasmine/spec/jasmine_examples/PlayerSpec.js:20:29)
at <Jasmine>
1 spec, 1 failure
But jasmine says it was not called.
If i remove the mocked Promise the test passes but i needed there. In another test i will return an error in the Promise and let it handle from another function.
So the Promise breaks the test but why?
The test executes synchronously and the expect fails before the callback queued by this.mine.getSource().then() has a chance to execute.
For Jasmine >= 2.7 and async function support you can convert your test function into an async function and add an await Promise.resolve(); where you want to pause the synchronous test and let any queued callbacks execute.
For your test it would look like this:
import Game from '../../lib/jasmine_examples/Game'
Game.mine = {}
describe("Game", function() {
it("should set source and handle it", async () => {
Game.mine.getSource = () => {
return new Promise((resolve)=>{
resolve( {
source : 'BAAAAR'
})
})
}
spyOn(Game, 'handle').and.callThrough();
Game.setSource();
await Promise.resolve(); // let the event loop cycle
expect(Game.handle).toHaveBeenCalled();
});
});
For older (>= 2.0) Jasmine versions you can use done() like this:
import Game from '../../lib/jasmine_examples/Game'
Game.mine = {}
describe("Game", function() {
it("should set source and handle it", (done) => {
Game.mine.getSource = () => {
return new Promise((resolve)=>{
resolve( {
source : 'BAAAAR'
})
})
}
spyOn(Game, 'handle').and.callThrough();
Game.setSource();
Promise.resolve().then(() => {
expect(Game.handle).toHaveBeenCalled();
done();
});
});
});
You can run the test inside fakeAsync and run tick() before the expect()
Service:
getFirebaseDoc() {
this.db.firestore.doc('some-doc').get()
.then(this.getFirebaseDocThen)
.catch(this.getFirebaseDocCatch);
}
Unit testing:
it('should call getFirebaseDocThen', fakeAsync(() => { // note `fakeAsync`
spyOn(service, 'getFirebaseDocThen');
spyOn(service.db.firestore, 'doc').and.returnValue({
get: (): any => {
return new Promise((resolve: any, reject: any): any => {
return resolve({ exists: true });
});
},
});
service.getFirebaseDoc();
tick(); // note `tick()`
expect(service.getFirebaseDocThen).toHaveBeenCalled();
}));

Calls to Dispatcher.register return `undefined`

new to Jest and having trouble with getting up and running. When calling Dispatcher.register, all I get is undefined.
I'm running Node.js 5.7.0 and Jest 0.9.2.
The following test fails:
BookedServicesStore-test.js:
jest.unmock("../BookedServicesStore");
jest.unmock("object-assign");
describe("BookedServicesStore", () => {
let BookedServicesStore;
let AppDispatcher;
beforeEach(() => {
BookedServicesStore = require("../BookedServicesStore");
AppDispatcher = require("../../dispatcher/AppDispatcher");
});
it("has a dispatch token", () => {
expect(BookedServicesStore.dispatchToken).not.toBeUndefined();
});
});
BookedServicesStore.js:
import AppDispatcher from "../dispatcher/AppDispatcher";
import { EventEmitter } from "events";
import assign from "object-assign";
const CHANGE_EVENT = "change";
const BookedServicesStore = assign({}, EventEmitter.prototype, {
emitChange() {
this.emit(CHANGE_EVENT);
},
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
// ...
});
BookedServicesStore.dispatchToken = AppDispatcher.register(action => {
// ...
});
module.exports = BookedServicesStore;
AppDispatcher.js:
import { Dispatcher } from "flux";
module.exports = new Dispatcher();
Not sure what's going on here. What is it that I'm missing?

Resources