Unit test not working with Midwayjs and Egg - jestjs

Unable to run test case in Midwayjs(Egg), below is the sample code, can you please let me know what exactly I am doing wrong here.
import { createApp, close, createHttpRequest } from "#midwayjs/mock";
import { Framework } from "#midwayjs/web";
import { Application } from "egg";
describe("test/controller/admin.test.ts", () => {
let app: Application;
beforeAll(async () => {
app = await createApp<Framework>();
});
afterAll(async () => {
await close(app);
});
it("should GET /api/test", async () => {
const result = await createHttpRequest(app)
.get("/api/test");
expect(result.status).toBe(200);
});
});
Below are the dependencies:
"egg": "^2.0.0",
"#midwayjs/core": "^3.0.0",
"egg-scripts": "^2.10.0"'
...
and dev-dependencies:
"#midwayjs/egg-ts-helper": "^1.0.1",
"#midwayjs/mock": "^3.0.0",
"#types/jest": "^27.4.1",
"egg-mock": "^3.20.0",
"jest": "^26.4.0",
"ts-jest": "^26.2.0"
...
Getting below error while running test:
FAIL test/controller/admin.test.ts (40.32 s)
test/controller/admin.test.ts
× should GET /api/test (2 ms)
● test/controller/admin.test.ts › should GET /api/test
Timeout - Async callback was not invoked within the 30000 ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 30000 ms timeout specified by jest.setTimeout.
at mapper (node_modules/jest-jasmine2/build/queueRunner.js:27:45)
● test/controller/admin.test.ts › should GET /api/test
TypeError: Cannot read property 'callback2' of undefined
22 | it("should GET /api/test", async () => {
> 24 | const result = await createHttpRequest(app)
| ^
25 | .get("/api/test");
26 | expect(result.status).toBe(200);
at createHttpRequest (node_modules/#midwayjs/mock/dist/client/http.js:6:13)

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.

Egg-Mock Getting can't get httpRequest before ready while running unit test

I am getting error while running test cases, below are the dependencies I am using, Can you please let me know what I am doing wrong here ?
"egg-mock": "^3.20.0",
"jest": "^26.4.0",
"egg": "^3.3.3",
"#midwayjs/core": "^3.0.0",
Below is the sample code snippet I am using in my test file:
import mock from 'egg-mock';
describe('some test', () => {
let app;
beforeEach(async () => {
app = mock.app();
await app.ready();
})
afterEach(() => app.close());
it('should request /api/test', () => {
const result = app.httpRequest()
.get('/api/test');
expect(result.status).toBe(200);
});
});
Below is the error I am getting while running the test case (yarn run test):
some test › should request /api/test
assert(received)
Expected value to be equal to:
true
Received:
false
at fixWinEPERM (node_modules/mz-modules/node_modules/rimraf/rimraf.js:175:5)
at node_modules/mz-modules/node_modules/rimraf/rimraf.js:149:7
● some test › should request /api/test
can't get httpRequest before ready
10 |
11 | it('should request /api/test', () => {
> 12 | const result = app.httpRequest()
| ^
13 | .get('/api/test');
14 |
15 | expect(result.status).toBe(200);
at Object.get (node_modules/egg-mock/lib/app.js:197:36)

Sinon stub replaces class property for whole test file instead of describe block

I'm trying to replace a static method of a class so that it returns custom value for testing purposes.
I have multiple test (describe) blocks in my file, and my goal is to replace the static method only in one of the describe blocks.
The problem is that the method is replaced for all the tests, even though I have a teardown method that is replacing the static method with the original method.
Some code:
class Cat {
static sound() {
return "Meow";
}
}
module.exports = {
Cat
}
const sinon = require("sinon");
const { Cat } = require("./myClass");
describe("Main tests", () => {
describe("Test Cat", () => {
it("Meows", () => {
expect(Cat.sound()).toBe("Meow")
})
})
describe("Test Dog", () => {
const stub = sinon
.stub(Cat, "sound")
.callsFake(() => "Woof");
afterAll(() => {
stub.restore();
});
it("Barks", () => {
expect(Cat.sound()).toBe("Woof")
})
})
})
Test results - the unreplaced test case is failing:
FAIL ./index.test.js
Main tests
Test Cat
✕ Meows (6ms)
Test Dog
✓ Barks
● Main tests › Test Cat › Meows
expect(received).toBe(expected) // Object.is equality
Expected: "Meow"
Received: "Woof"
7 | describe("Test Cat", () => {
8 | it("Meows", () => {
> 9 | expect(Cat.sound()).toBe("Meow")
| ^
10 | })
11 | })
12 |
Is there a way how to prevent this?
I tried using createSandbox:
const sandbox = sinon.createSandbox()
const stub = sandbox
.stub(Cat, "sound") // etc
but it's the same thing.
Any help would be appreciated.
This task can be done easily with jestjs only (without sinon).
Just use jest.spyOb function to spy sound function, and you can mock the result of this function:
const { Cat } = require('./myClass');
describe('Main tests', () => {
beforeEach(() => {
jest.spyOn(Cat, 'sound');
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Test Cat', () => {
it('Meows', () => {
// Don't mock, just call actual logic
expect(Cat.sound()).toBe('Meow');
});
});
describe('Test Dog', () => {
it('Barks', () => {
Cat.sound.mockReturnValue('Woof'); // mock return value of `sound()`
expect(Cat.sound()).toBe('Woof');
});
});
});

nodejs unit test PubSub.publish not sending data

I'm having issues doing a unit test with the following environment:
"pubsub-js": "^1.9.2",
"#types/chai": "^4.2.14",
"#types/mocha": "^8.2.0",
"chai": "^4.2.0",
"firebase-functions-test": "^0.2.3",
"mocha": "^8.2.1",
"ts-node": "^9.1.1",
"ts-sinon": "^2.0.1",
When publish a message to my pubsub the data received by it is always: Message { data: undefined, attributes: {}, _json: undefined } and I can't figure out why.
There is some code to describe my scenario:
pubsub-myFunc.ts
export const pubsubMyFunc= functions.pubsub
.topic("on-myTopic")
.onPublish(async (message) => {
console.log("version 1")
console.log(message)
/**
* Received message from topic
*/
const myMessage = Buffer.from(message.data, "base64").toString("utf-8")
pubsub-myFunc.spec.ts
import * as functions from 'firebase-functions';
import * as PubSub from 'pubsub-js';
import * as tsSinon from 'ts-sinon';
import { pubsubMyFunc } from './pubsub-myFuncr';
import * as sendEmail from './send-mail';
describe("PubSub tests", () => {
beforeEach(() => {
process.env.GCLOUD_PROJECT = "my env"
})
it("Should call sendNotificationMessage", function (done) {
// this.timeout(60000)
const today = new Date()
const data = {
dateCreated: today,
expireDate: today.getDate() + 30,
objId: "id",
objParentId: "parentId",
}
const spy = tsSinon.default.spy(sendEmail, "sendNotificationMessage")
const dataBuffer = Buffer.from(JSON.stringify(data))
const pubsubMessage = new functions.pubsub.Message(dataBuffer)
PubSub.subscribe("on-myTopic", pubsubMyFunc)
console.log("publish")
PubSub.publish("on-myTopic", pubsubMessage)
setTimeout(() => {
// check if spy was called
tsSinon.default.assert.calledOnce(spy)
done()
}, 15000)
})
})
I have tried to pass directly the dataBuffer but without any luck as well, the outputs of my console logs are:
publish
version 1
Message { data: undefined, attributes: {}, _json: undefined }
Is there any reason for my Message.data to be undefined?
I'm not sure if pubsub-js's subscribe function is compatible with the output of firebase-functions' onPublish method.
You could use firebase's emulators to run your functions locally and test them by sending messages like you would normally instead of going through pubsub-js.
export const pubsubMyFunc= functions.pubsub
.topic("on-myTopic")
.onPublish(async (message) => {
console.log("version 1");
console.log(message);
/**
* Received message from topic
*/
const myMessage = message.json;
...
Make sure pubsubMyFunc is being exported at firebase functions entry point.
Install and configure firebase emulators, make sure PUBSUB_EMULATOR_HOST env var is set to localhost:$PORT where $PORT is set to the port you have configured for pub sub emulator, located in firebase.json under emulators.pubsub.port.
Once that's all setup, you can send a message to pubsub by:
import {PubSub} from "#google-cloud/pubsub";
cont getTopic = () => {
const topic = new PubSub().topic("on-myTopic");
topic.exists().then(([exists]) => exists
? topic
: topic.create().then(([newTopic]) => newTopic)
);
};
describe("PubSub tests", () => {
let topic;
beforeEach(async() => {
process.env.GCLOUD_PROJECT = "my env"
topic = await getTopic();
});
it("should test something", async() => {
await topic.publishJSON(const data = {
dateCreated: today,
expireDate: today.getDate() + 30,
objId: "id",
objParentId: "parentId",
});
// rest of your code here.
});
});
This will work on the process of hack
export const pubsubMyFunc= functions.pubsub
.topic("on-myTopic")
.onPublish(async (message) => {
console.log("version 1");

Jest mock submodule function

I have this node module called client. It has the following structure:
//index.js
import users from "./users"
export default { users }
And:
//users.js
export default { getUser }
function getUser(username) {
...
return {role: userRole}
}
I want to mock this getUser(username) function in my tests.
So I could call something like:
client.users.getUser.mockResolvedValueOnce({role: "manager"})
My test header is like:
let client = jest.genMockFromModule('client').default;
client.users.getUser = jest.fn();
But, running my test, I get the following error when my original code call client.users.getUser.
TypeError: Cannot read property 'users' of undefined
58 |
59 | // Retrieve user and then return its projects
> 60 | client.users.getUser(username)
| ^
61 | .then(user => {
62 | if (!user) {
63 | throw new Error(`User ${username} not found`)
at Object.getUser (node_modules/client/build/users.js:26:45)
at Object.getUser [as insert] (src/web/controller/projects.js:60:18)
at Object.insert (src/web/controller/projects.test.js:80:18)
You can just mock the //users.js module like this:
jest.mock('./users.js', () => ({getUser: () => ({role: "manager"})})) //note that the path is relative to the test
if you need different return values during your tests you can mock it to return a spy and set the mock return value in the tests:
import {getUsers} from './users'
jest.mock('./users.js', () => ({getUser: jest.fn()}))
it('does something', () => {
getUser.mockImplementation(() => ({role: "manager"}))
})
it('does something else', () => {
getUser.mockImplementation(() => ({role: "user"}))
})

Resources