Jasmine expecting a spy, but got a Function - node.js

I'm stuck being stubborn to give up and find a workaround for something that looks so simple and should be working out of the box...
I'm dealing with a Jasmine test suite using the Node environment, that is unable to spy upon an object that is clearly there/provided/imported.
I assume it's about the module resolution, and the writable property...
Can someone point me out in the proper direction, please?
For what it's worth: using Angular with testBed and stuff I never had such issues with spies, they work like...they're out of the box.
Notes:
using prototype to spy on did not fix: spyOn(client.prototype,'clone').and.callThrough();
and as you can see below, nothing gets overwritten.
Below a simplified implementation to demo.
index.js:
`
let state = null;
const templates = { obj: { a:1, b:2} },
init = () => state = clone(templates.obj),
clone = (obj) => JSON.parse(JSON.stringify(obj));
export {
init,
clone
}`
index.spec.js:
`
import * as client from '../../index.js';
describe("test", () => {
it("should spy on the clone method", () => {
console.log(Object.getOwnPropertyDescriptors(client));
spyOn(client, 'clone').and.callThrough();
client.init();
expect(client.clone).toHaveBeenCalled();
});
})`
test result:
`
> client#1.0.0 test
> jasmine
Randomized with seed 24629
Started
client.clone: [Function: clone]
{
clone: {
value: [Function: clone],
writable: true,
enumerable: true,
configurable: false
},
init: {
value: [Function: init],
writable: true,
enumerable: true,
configurable: false
},
[Symbol(Symbol.toStringTag)]: {
value: 'Module',
writable: false,
enumerable: false,
configurable: false
}
}
F
Failures:
1) test should spy on the clone method
Message:
Error: <toHaveBeenCalled> : Expected a spy, but got Function.
Usage: expect(<spyObj>).toHaveBeenCalled()`

index.spec.js
import * as importedModule from '../../index.js';
const client = Object.assign({}, importedModule);
describe("test", () => {
it("should spy on the clone method", () => {
spyOn(client, 'clone').and.callThrough();
client.clone({a:1,b:2});
expect(client.clone).toHaveBeenCalled();
});
})
test result:
client#1.0.0 test
jasmine
Randomized with seed 17205
Started
.
1 spec, 0 failures
Finished in 0.007 seconds
Randomized with seed 17205 (jasmine --random=true --seed=17205)

index.js
let state = null;
const templates = { obj: { a:1, b:2} },
init = () => state = clone(templates.obj),
clone = (obj) => JSON.parse(JSON.stringify(obj));
export {
init,
clone
}
index.spec.js
import * as client from '../../index.js';
describe("test", () => {
it("should spy on the clone method", () => {
spyOn(client, 'clone').and.callThrough();
client.clone({a:1,b:2});
expect(client.clone).toHaveBeenCalled();
});
});
test result
client#1.0.0 test
jasmine
Randomized with seed 76297
Started
F
Failures:
1. test should spy on the clone method
Message:
Error: : Expected a spy, but got Function.
Usage: expect().toHaveBeenCalled()
Stack:
at
at UserContext. (file:///home/masterjones/Projects/test/spec/support/index.spec.js:7:30)
at

Related

Jest SuperTest: Giving error in Open Handler when using setTimeout in the class-under-test

i am writing a E2E in Jest/Supertest in Nest JS (node) environment.
I have searched extensively about this pretty common error:
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
Implemented every solutions suggested.
However getting this in a setTimeout method intermittently as follows:
Test Suites: 2 passed, 2 total
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: 135.464 s
Ran all test suites.
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● Timeout
49 | },
50 | delay(millisecond: number) {
> 51 | return new Promise((resolve) => setTimeout(resolve, millisecond));
| ^
52 | },
53 | };
54 |
Here is my setTimeout code:
// The API
async create<T>(key: string, item: T): Promise<T> {
await this.delay(5000);
return this.createWithoutDelay(key, item);
}
// The delay method code
delay(millisecond: number) {
return new Promise((resolve) => setTimeout(resolve, millisecond));
},
My tests are very simple:
beforeAll(async () => {
jest.clearAllMocks();
......
app = moduleUnderTest.createNestApplication();
await app.init();
// Reference the server instance
server = app.getHttpServer();
});
it('Test: Create Object', async () => {
const response = await request(server).post('/object')
.send({
name: 'new-object',
});
expect(response.status).toEqual(HttpStatus.CREATED);
expect(response.body).toBeDefined();
expect(Array.isArray(response.body)).toBeFalsy();
const jsonResponse = JSON.parse(response.text);
expect(jsonResponse.name).toEqual('new-object');
});
afterAll(async () => {
await app.close();
await server.close();
});
The following are my Jest config:
module.exports = {
moduleFileExtensions: ["js", "json", "ts"],
verbose: true,
preset: "ts-jest",
rootDir: ".",
testTimeout: 15000,
fakeTimers: {
timerLimit: 15000,
},
testEnvironment: "node",
testRegex: ".e2e-spec.ts$",
transform: {
"^.+\\.(t|j)s$": "ts-jest"
},
moduleNameMapper: {
'^axios$': require.resolve('axios'),
},
};
In the Jest cmd, I am passing: --runInBand --forceExit --detectOpenHandles
Any clue shall be helpful.

How to mock a quasar component

This is my code for a quasar component that I want to mock
emits: [
"buyer-protection",
...useDialogPluginComponent.emits
]
But I get the following error:
TypeError: _quasar.useDialogPluginComponent.emits is not iterable
I'd like to mock useQuasar and usePluginDialogComponent from the quasar module. I tried to mock them this way:
jest.mock('quasar', () => ({
useDialogPluginComponent: () => ({
emits: []
}),
useQuasar: () => ({
platform: {
is: {
desktop: true
}
}
})
}))
How can I mock these quasar components?
I tried to mock useDialogPluginComponent, too.
But I realized that installQuasarPlugin() will handle all Quasar plugins, so I don't need to mock any plugin by myself.
But It has to create the instance by mount, don't use shallowMount, that is make sure dialogRef = vue.ref(null) will catch q-dialog.
For my example, I want to check 'onDialogOK' has been called, but I can't track 'onDialogOK' by spyOn, so I check the wrapper.vm emitted.
expect(vueWrapper.emitted()).toHaveProperty('ok');
It works.
I am not sure why useDialogPluginComponent is both a function instance and object instance.
As a workaround, I have defined a function and assigned emits object to it.
jest.mock("quasar", () => {
let t1 = () => {
return {
dialogRef: {},
onDialogHide: () => {},
onDialogOK: () => {},
onDialogCancel: () => {}
}
};
t1.emits = ['ok', 'hide'];
return {
useQuasar: () => ({
platform: {
is: {
Mobile: true,
},
},
}),
useDialogPluginComponent: t1
}
});

Why is the `message` for my Jest custom matcher not being displayed?

I've created a Jest custom matcher. It works (meaning, it passes/fails when it should), but I don't see the message anywhere in Jest's output.
What am I doing wrong? Do I have to do something to "enable" messages? Am I totally misunderstanding where the message is supposed to show up?
Environment: NestJS, Prisma
Execution command: jest --watch
Simplified code:
declare global {
namespace jest {
interface Matchers<R> {
toMatchHash(received: string, expected: string): R;
}
}
}
expect.extend({
toMatchJsonHash(received, expected) {
return {
pass: false,
message: () => `Why doesn't this work?!`,
};
},
});
expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
Output:
● MyService › should pass a test using a custom matcher
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: toMatchJsonHash<db0110285c148c77943f996a17cbaf27>
Received: {<Big ol' object redacted for conciseness>}
Number of calls: 1
178 |
179 | // #ts-ignore
> 180 | expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
| ^
181 | // expect(prisma.name.findMany).toHaveBeenCalledWith({
182 | // select: { type: true, name: true },
183 | // where: {
at Object.<anonymous> (my/my.service.spec.ts:180:32)
I'm expecting to see "Why doesn't this work?!" somewhere in the output, but I don't. What am I missing?
As suggested by #jonsharpe, the reason was that Jest was showing the message from the "outer" matcher, .toHaveBeenCalledWith().
To fix this, I found the source that defines the .toHaveBeenCalledWith() matcher and "merged" its code into my custom matcher.
This enabled my custom matcher to effectively "extend" the functionality of the .toHaveBeenCalledWith() matcher, including my own custom code and messages.
In case it helps someone, the code I ended up with for my specific use case was:
declare global {
namespace jest {
interface Matchers<R> {
toHaveBeenCalledWithObjectMatchingHash(expected: string): CustomMatcherResult;
}
}
}
expect.extend({toHaveBeenCalledWithObjectMatchingHash(received, expected) {
const isSpy = (received: any) =>
received != null &&
received.calls != null &&
typeof received.calls.all === 'function' &&
typeof received.calls.count === 'function';
const receivedIsSpy = isSpy(received);
const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
const calls = receivedIsSpy
? received.calls.all().map((x: any) => x.args)
: received.mock.calls;
if(calls.length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was not called.`,
};
}
if(calls[0].length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was called, but not with any arguments.`,
};
}
const md5Hash = crypto.createHash('md5');
const receivedHash = md5Hash.update(JSON.stringify(calls[0][0])).digest('hex');
const pass = receivedHash === expected;
if(pass) {
return {
pass: true,
message: () => `expected the function to not be called with an object that hashes to '${expected}'. Instead, the passed object hashes to the same value.`,
};
} else {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the passed object hashes to '${receivedHash}'.`,
};
}
}});

Testing if external component method is called in jest

I am using jest and enzyme for unit testing. Below is my index.js file. I need to test openNotification and uploadErrorNotification function of the file. However, only uploadErrorNotification function is exported. So, How do I test both the functions.
Also, I don't want to use any other libray except jest and enzyme.
//index.js
import {
notification
} from 'antd';
const openNotification = (message, description, className) => {
notification.open({
key: 'upload-template',
message,
description,
placement: "bottomRight",
duration: null,
});
};
const uploadErrorNotification = (uploadFailedText, errorMsg) => {
openNotification(uploadFailedText, errorMsg, 'error');
};
export {
uploadErrorNotification
}
This is my test file:
//test.js
import { uploadErrorNotification } from '../index.js
jest.mock('notification', () => ({ open: () => jest.fn() })); // was trying this but I couldn't understand how it will work
describe('Notification validation functions testing', () => {
uploadErrorNotification('Upload failed', 'Something went wrong.');
expect("openNotification").toHaveBeenCalledTimes(1); // want to do something like this
});
You have to mock the external depenency:
first mock antd so that notification.open is a spy
jest.mock('antd', () => ({notification: open: {jest.fn()}}))
Then import the module into your test
import { notification } from 'antd';
Know you can use it like this:
expect(notification.open).toHaveBeenCalledTimes(1);
If you want to test notification without overwrite other antd component, you can add jest.requireActual('antd').
jest.mock('antd', () => {
return {
...jest.requireActual('antd'),
notification: {
open: jest.fn(),
},
};
});

Dynamically Running Mocha Tests

I'm trying to run a series of tests dynamically. I have the following setup but it doesn't seem to run and I'm not getting any errors:
import Mocha from 'mocha';
const Test = Mocha.Test;
const Suite = Mocha.Suite;
const mocha = new Mocha();
for (let s in tests) {
let suite = Suite.create(mocha.suite, s);
tests[s].forEach((test) => {
console.log('add test', test.name)
suite.addTest(new Test(test.name), () => {
expect(1+1).to.equal(2);
});
});
}
mocha.run();
The tests I'm running look like this:
{ todo:
[ { name: 'POST /todos',
should: 'create a new todo',
method: 'POST',
endpoint: '/todos',
body: [Object] } ] }
(though at this point my test is just trying to check a basic expect)
Based on the console.logs the iteration seems fine and it appears to be adding the tests, so I'm confident in the flow of operations, I just can't get any execution or errors.
You have to pass the test function to the Test constructor, not to suite.addTest. So change your code to add your tests like this:
suite.addTest(new Test(test.name, () => {
expect(1+1).to.equal(2);
}));
Here is the entire code I'm running, adapted from your question:
import Mocha from 'mocha';
import { expect } from 'chai';
const Test = Mocha.Test;
const Suite = Mocha.Suite;
const mocha = new Mocha();
var tests = { todo:
[ { name: 'POST /todos',
should: 'create a new todo',
method: 'POST',
endpoint: '/todos',
body: [Object] } ] };
for (let s in tests) {
let suite = Suite.create(mocha.suite, s);
tests[s].forEach((test) => {
console.log('add test', test.name);
suite.addTest(new Test(test.name, () => {
expect(1+1).to.equal(2);
}));
});
}
mocha.run();
When I run the above with node_modules/.bin/babel-node test.es6, I get the output:
todo
✓ POST /todos
1 passing (5ms)
It's critical to test your test system and make sure it deals with passing and failing tests and thrown exceptions.
Since folks are counting on a build process to warn them about errors, you must also set the exit code to a non-zero if anything failed.
Below is a test script (which you must invoke with node test.js rather than mocha test.js) which exercises all paths through your test suite:
const Mocha = require('mocha')
const expect = require('chai').expect
var testRunner = new Mocha()
var testSuite = Mocha.Suite.create(testRunner.suite, 'Dynamic tests')
var tests = [ // Define some tasks to add to test suite.
{ name: 'POST /todos', f: () => true }, // Pass a test.
{ name: 'GET /nonos', f: () => false }, // Fail a test.
{ name: 'HEAD /hahas', f: () => { throw Error(0) } } // Throw an error.
]
tests.forEach(
test =>
// Create a test which value errors and caught exceptions.
testSuite.addTest(new Mocha.Test(test.name, function () {
expect(test.f()).to.be.true
}))
)
var suiteRun = testRunner.run() // Run the tests
process.on('exit', (code) => { // and set exit code.
process.exit(suiteRun.stats.failures > 0) // Non-zero exit indicates errors.
}) // Falling off end waits for Mocha events to finish.
Given that this is prominent in web searches for asynchronous mocha tests, I'll provide a couple more useful templates for folks to copy.
Embedded execution: The first directly adds tests which invoke an asynchronous faux-network call and check the result in a .then:
const Mocha = require('mocha')
const expect = require('chai').expect
var testRunner = new Mocha()
var testSuite = Mocha.Suite.create(testRunner.suite, 'Network tests')
var tests = [ // Define some long async tasks.
{ name: 'POST /todos', pass: true, wait: 3500, exception: null },
{ name: 'GET /nonos', pass: false, wait: 2500, exception: null },
{ name: 'HEAD /hahas', pass: true, wait: 1500, exception: 'no route to host' }
]
tests.forEach(
test =>
// Create a test which value errors and caught exceptions.
testSuite.addTest(new Mocha.Test(test.name, function () {
this.timeout(test.wait + 100) // so we can set waits above 2000ms
return asynchStuff(test).then(asyncResult => {
expect(asyncResult.pass).to.be.true
}) // No .catch() needed because Mocha.Test() handles them.
}))
)
var suiteRun = testRunner.run() // Run the tests
process.on('exit', (code) => { // and set exit code.
process.exit(suiteRun.stats.failures > 0) // Non-zero exit indicates errors.
}) // Falling off end waits for Mocha events to finish.
function asynchStuff (test) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// console.log(test.name + ' on ' + test.endpoint + ': ' + test.wait + 'ms')
if (test.exception)
reject(Error(test.exception))
resolve({name: test.name, pass: test.pass}) // only need name and pass
}, test.wait)
})
}
This code handles passing and failing data, reports exceptions, and exits with a non-zero status if there were errors. The output reports all expected problems and additionally whines about the test taking a like time (3.5s):
Network tests
✓ POST /todos (3504ms)
1) GET /nonos
2) HEAD /hahas
1 passing (8s)
2 failing
1) Network tests GET /nonos:
AssertionError: expected false to be true
+ expected - actual
-false
+true
2) Network tests HEAD /hahas:
Error: no route to host
Delayed execution: This approach invokes all of the slow tasks before populating and starting the the mocha test suite:
const Mocha = require('mocha')
const expect = require('chai').expect
var testRunner = new Mocha()
var testSuite = Mocha.Suite.create(testRunner.suite, 'Network tests')
var tests = [ // Define some long async tasks.
{ name: 'POST /todos', pass: true, wait: 3500, exception: null },
{ name: 'GET /nonos', pass: false, wait: 2500, exception: null },
{ name: 'HEAD /hahas', pass: true, wait: 1500, exception: 'no route to host' }
]
Promise.all(tests.map( // Wait for all async operations to finish.
test => asynchStuff(test)
.catch(e => { // Resolve caught errors so Promise.all() finishes.
return {name: test.name, caughtError: e}
})
)).then(testList => // When all are done,
testList.map( // for each result,
asyncResult => // test value errors and exceptions.
testSuite.addTest(new Mocha.Test(asyncResult.name, function () {
if (asyncResult.caughtError) { // Check test object for caught errors
throw asyncResult.caughtError
}
expect(asyncResult.pass).to.be.true
}))
)
).then(x => { // When all tests are created,
var suiteRun = testRunner.run() // run the tests
process.on('exit', (code) => { // and set exit code.
process.exit(suiteRun.stats.failures > 0) // Non-zero exit indicates errors.
})
})
function asynchStuff (test) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
// console.log(test.name + ' on ' + test.endpoint + ': ' + test.wait + 'ms')
if (test.exception)
reject(Error(test.exception))
resolve({name: test.name, pass: test.pass}) // only need name and pass
}, test.wait)
})
}
The output is the same except that mocha doesn't whine about the slow test and instead believes the tests tool less than 10ms. The Promise.all waits for all the promises to resolve or reject then creates the tests to validate the results or report exceptions. This is a few lines longer than Embedded execution because it must:
Resolve exceptions so Promise.all() resolves.
Execute the tests in a final Promise.all().then()
Comments describing how folks pick which style to use could guide others. Share your wisdom!

Resources