Why does Jest rise this error while testing `Colyseus` game? - javascript

I am testing a game functionality with Jest as a testing framework and assertion library.
This is my test setup:
const { Server } = require("colyseus")
const { ColyseusTestServer, boot } = require("#colyseus/testing");
const Game = require("./index");
describe("Game", () => {
let colyseus;
let TestServer = new Server()
TestServer.define("my_game", Game)
const User_1 = {
key: "token_header.token_payload.and_oh"
}
beforeAll(async () => colyseus = await boot(TestServer));
afterAll(async () => await colyseus.shutdown());
beforeEach(async () => await colyseus.cleanup());
test("Should connect", async () => {
try {
// `room` is the server-side Room instance reference.
const room = await colyseus.sdk.joinOrCreate("my_game", {...User_1});
await room.waitForNextPatch();
await room.waitForNextMessage();
// `client1` is the client-side `Room` instance reference (same as JavaScript SDK)
// make your assertions
expect(room.sessionId).toEqual(room.clients[0].sessionId);
} catch(err) {
console.log("errr",err)
}
})
})
So every time that I run my tests Jest logs these errors:
Game
✕ Should connect (8003 ms)
● Game › Should connect
TypeError: decode[type] is not a function
at decodePrimitiveType (node_modules/#colyseus/schema/src/Schema.ts:109:34)
at _.Object.<anonymous>.Schema.decode (node_modules/#colyseus/schema/src/Schema.ts:352:25)
at SchemaSerializer.Object.<anonymous>.SchemaSerializer.setState (node_modules/colyseus.js/src/serializer/SchemaSerializer.ts:10:20)
at Room.Object.<anonymous>.Room.setState (node_modules/colyseus.js/src/Room.ts:225:25)
at Room.Object.<anonymous>.Room.onMessageCallback (node_modules/colyseus.js/src/Room.ts:203:18)
at WebSocket.onMessage (node_modules/ws/lib/event-target.js:132:16)
at Receiver.receiverOnMessage (node_modules/ws/lib/websocket.js:983:20)
at Receiver.dataMessage (node_modules/ws/lib/receiver.js:502:14)
at Receiver.getData (node_modules/ws/lib/receiver.js:435:17)
at Receiver.startLoop (node_modules/ws/lib/receiver.js:143:22)
at Receiver._write (node_modules/ws/lib/receiver.js:78:10)
at Socket.socketOnData (node_modules/ws/lib/websocket.js:1077:35)
● Game › Should connect
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
18 | beforeEach(async () => await colyseus.cleanup());
19 | jest.setTimeout(8000)
> 20 | test("Should connect", async () => {
| ^
21 | try {
22 | // `room` is the server-side Room instance reference.
23 | const room = await colyseus.sdk.joinOrCreate("my_game", {...User_1});
at server/test.js:20:5
at Object.<anonymous> (server/test.js:7:1)
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:401:19)
at _run10000 (node_modules/#jest/core/build/cli/index.js:320:7)
at runCLI (node_modules/#jest/core/build/cli/index.js:173:3)
I changed the timeouts for Jest but it did not change anything.
These are versions for Colyseus, Jest, and Node.js which I am using:
"#colyseus/schema": "^1.0.28",
"#colyseus/testing": "^0.14.22",
"colyseus": "^0.14.18",
"jest": "^27.3.1"
node: v14.7

Leaving it there for those struggling with timeout problems (which I had while testing). Mostly it's when no patch is sent after waitForNextPatch() is called, in my case it was either because
nothing has changed
problem at patch sending / serialisation: considering your other issue it's probably was why you had a timeout exception
some weird async situation, calling waitForXXX() too late while the patch has already passed. Unlikely when using in testing situation like this and no real network is involved

This issue was due to an undetermined field type of the synced state's schema of colyseus game framework that had been added to the last versions of the framework. so I just defined the type as playerId: [string] and It resolved without any error.

Related

Jest mock function is called but expect.toHaveBeenCalled() fails

I know that there are already questions about this but I can't find a definite answer. I am using SvelteKit and I tried to mock $app/navigation likes this in setup file.
jest.mock('$app/navigation', () => {
return {
__esModule: true,
goto: jest.fn().mockImplementation((target) => console.log(target))
};
});
I test a component that call goto. It is indeed called because there is a console.log call in the test output. When I tried to test it with expect(goto).toHaveBeenCalled(), it fails.
// SvelteKit
import * as navigations from '$app/navigation';
it('show error when account does not exists', async () => {
// render is in before Each
await fireEvent.change(screen.getByLabelText('Email'), {
target: { value: 'example#email.com' }
});
await fireEvent.change(screen.getByLabelText('Password'), {
target: { value: 'B#adPass0rd' }
});
await fireEvent.click(screen.getByRole('button'));
// There is no problem. It should redirect.
expect(navigations.goto).toHaveBeenCalled();
});
Output
console.log
/success
at log (jest-setup.js:6:58)
FAIL src/lib/routes-tests/login.test.js
Login
✕ show error when account does not exists (23 ms)
● Login › show error when account does not exists
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
24 | await fireEvent.click(screen.getByRole('button'));
25 | // expect(screen.queryByText('Account does not exist')).not.toBeNull();
> 26 | expect(navigations.goto).toHaveBeenCalled();
| ^
27 | });
28 | });
29 |
at toHaveBeenCalled (src/lib/routes-tests/login.test.js:26:28)
at tryCatch (src/lib/routes-tests/login.test.js:23:2404)
at Generator._invoke (src/lib/routes-tests/login.test.js:23:1964)
at Generator.next (src/lib/routes-tests/login.test.js:23:3255)
at asyncGeneratorStep (src/lib/routes-tests/login.test.js:25:103)
at _next (src/lib/routes-tests/login.test.js:27:194)
It turns out that I called goto in async function. I must use waitFor to expect the change.
await waitFor(() => expect(navigations.goto).toHaveBeenCalled())

How To Configure Cypress To Wait Longer (or Indefinitely) for BaseUrl?

I am using this Cypress image in a docker-compose.yml to run end to end tests: cypress/included:6.1.0
When the test runner starts it will verify that a server can be reached at baseUrl. If not, it retries 3 times.
My services and web server need a little more time to start.
How can I increase the timeout and/or number of retries for this check.
Preferably, in my case I'd like a retry-until-success policy, i.e. indefinite retries/wait.
I have checked the Timeouts section and the cypress.json documentation more generally. However none of those timeouts or retries seem to relate to this behavior.
Is there a setting for this?
TO CLARIFY: This is not a check I implemented (or want to) as part of my specs. This is, as far as I can tell so far, a feature of cyprus run, the default command in the image. If possible I would like to configure this without adding to or revising the tests themselves.
Here is the docker-compose console output when cypress starts in the container:
cypress_1 | Cypress could not verify that this server is running:
cypress_1 |
cypress_1 | > http://localhost:5000
cypress_1 |
cypress_1 | We are verifying this server because it has been configured as your `baseUrl`.
cypress_1 |
cypress_1 | Cypress automatically waits until your server is accessible before running tests.
cypress_1 |
cypress_1 | We will try connecting to it 3 more times...
cypress_1 | We will try connecting to it 2 more times...
cypress_1 | We will try connecting to it 1 more time...
cypress_1 |
cypress_1 | Cypress failed to verify that your server is running.
cypress_1 |
cypress_1 | Please start this server and then run Cypress again.
cypress_1 exited with code 1
You should ensure your server is running before calling cypress run using a utility like wait-on or start-server-and-test.
Cypress's check on the baseUrl is a final courtesy check just so you don't run through your whole test suite on a server that is not running.
For tips on ensuring your server is running before running Cypress, check out the Cypress docs here: https://on.cypress.io/continuous-integration#Boot-your-server
Disclaimer
#jennifer-shehane is correct; in most cases you should ensure that the server is running before starting cypress.
However
We're using Cypress primarily for API testing (for now), and one of the workflows we need to support includes a server restart. Of course, we could just drop in an arbitrarily long cy.wait(30000); before moving on to the next step, but that's inelegant and can waste a lot of time, especially if you're like me and end up running the tests over and over.
Since Cypress doesn't really work in the same asynchronous way we're usually accustomed to, the solution we came up with is to use a task.
Add this to plugins/index.js:
const https = require("https");
const { URL } = require("url");
/**
* #type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
require('#cypress/code-coverage/task')(on, config)
on("task", {
async waitForServerResponse({ server_url }) {
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makeRequest({ hostname, port, path }) {
return new Promise((resolve, reject) => {
const options = {
hostname,
port,
path,
body: {},
method: 'POST'
}
const req = https.request(options, response => {
response.on('data', d => {
resolve(d.toString());
});
});
req.on('error', error => {
reject(error);
});
req.end();
});
}
async function recursiveGet(retry = 1) {
try {
const res = await makeRequest({ hostname, port, path });
if (res?.code?.includes("ECONNREFUSED") || res?.code?.includes("ECONNRESET")) {
await sleep(1000);
await recursiveGet(retry + 1);
}
}
catch(error) {
if (error?.code?.includes("ECONNREFUSED") || error?.code?.includes("ECONNRESET")) {
await sleep(1000);
await recursiveGet(retry + 1);
}
}
}
if (!server_url) {
server_url = config.baseUrl;
}
const parsedUrl = new URL(server_url);
const hostname = parsedUrl?.hostname ?? "localhost";
const port = parsedUrl?.port ?? 443;
const path = parsedUrl?.pathname ?? "/";
return new Promise(async (resolve, reject) => {
// tasks should not resolve with undefined
setTimeout(() => reject(new Error("Timeout")), 60000);
await recursiveGet();
resolve(true);
});
}
});
return config;
};
and call it in your test:
it("Restarts the server", () => {
// Restart the server
cy.systemRestart().then(({ body, status }) => { // server returns success before actually restarting
expect(status).to.equal(200);
expect(body).property("success").to.eq(true);
cy.wait(1000); // initial wait
cy.task("waitForServerResponse", { server_url: server_url + "/auth/token" });
cy.login();
cy.adminIndex().then(({ body, status }) => {
if (body?.properties) {
expect(status).to.equal(200);
expect(body).property("properties").to.be.a("object");
const bootedAt = new Date(body.properties.system.bootedAt).getTime();
const now = new Date().getTime();
const diff = Math.ceil(Math.abs(now - bootedAt) / 1000); // ms -> s
expect(diff).to.be.lessThan(20); // seconds
}
});
});
});
This will poll the server (any given endpoint, I chose /auth/token) and if the connection is refused or reset, it will wait for 1 second and try again. The task will only return once it receives a response from the server.

Import a file after the Jest environment has been torn down

I'm making a simple API with Express and I'm trying to add tests with Jest but when I try to run the tests it displays the next error:
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at BufferList.Readable (node_modules/readable-stream/lib/_stream_readable.js:179:22)
at BufferList.Duplex (node_modules/readable-stream/lib/_stream_duplex.js:67:12)
at new BufferList (node_modules/bl/bl.js:33:16)
at new MessageStream (node_modules/mongodb/lib/cmap/message_stream.js:35:21)
at new Connection (node_modules/mongodb/lib/cmap/connection.js:52:28)
/home/jonathangomz/Documents/Node/Express/Devotionals/node_modules/readable-stream/lib/_stream_readable.js:111
var isDuplex = stream instanceof Duplex;
^
TypeError: Right-hand side of 'instanceof' is not callable
I'm not sure to trust the result if right after jest break (or something like that):
My test is:
const app = require("../app");
const request = require("supertest");
describe("Testing root router", () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});
My jest configuration on package.json:
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
}
Notes:
I read about jest.useFakeTimers() but It's not working and I'm not sure if I'm using in the wrong way. I also tried adding it to the beforeEach method but nothing.
In my case, I had to add the package to transformIgnorePatterns in the jest config.
Add jest.useFakeTimers('modern') before the asynchronous call. Add jest.runAllTimers() after the asynchronous call. This will fast-forward timers for you.
const app = require("../app")
const request = require("supertest")
describe("Testing root router", () => {
test("Should test that true === true", async () => {
//Before asynchronous call
jest.useFakeTimers("modern")
const response = await request(app).get("/")
//After asynchronous call
jest.runAllTimers()
expect(response.status).toBe(200)
})
})
Try adding --testTimeout=10000 flag when calling jest, it works for me.
Information based on Testing NodeJs/Express API with Jest and Supertest
--testTimeout flag - This increases the default timeout of Jest which is 5000ms. This is important since the test runner needs to refresh the database before running the test
By adding jest.useFakeTimers() just after all your import.
What about making your test async ?
const app = require("../app");
const request = require("supertest");
describe("Testing root router",async () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});

Jest - Testing for AWS Lambda

I'm trying to use Jest with my NodeJS based AWS Lambda function to test that the Lambda handler function is returning specific status codes. It seems like using lambda-tester is the easiest way to achieve this.
handler.test.js
const handler = require('../src/handler.js');
const LambdaTester = require('lambda-tester');
test('should return a StatusCode of 400 due to requirement not being met', function (done) {
return LambdaTester(handler.run)
.event({})
.expectResult(result => {
expect(result.statusCode).toBe(400);
});
});
I thought that would be pretty straightforward however I get this as a result:
expect(received).toBe(expected) // Object.is equality
Expected: 400
Received: 500
7 | .event({})
8 | .expectResult(result => {
> 9 | expect(result.statusCode).toBe(400);
| ^
10 | });
11 | });
12 |
at tests/handler.test.js:9:33
at runVerifier (node_modules/lambda-tester/lib/runner.js:121:16)
at LambdaRunner.run (node_modules/lambda-tester/lib/runner.js:277:23)
If I change the expect line to check for a statuscode of 500 I get an Async timeout:
Timeout - Async callback was not invoked within the 5000 ms timeout
specified by jest.setTimeout.Timeout
Any ideas as to why this isn't working?
The lambda should fail instantly because the event being passed in doesnt have a required attribute and it indeed does when tested via Postman.
Edit:
This lambda is part of a serverless application and could be tested locally, how could I use Jest to do that?
I was over thinking it, the below is working well:
test('should return a StatusCode of 400 due to requirement not being met', async () => {
const event = {};
const context = {};
const result = await handler.run(event, context);
expect(result.statusCode).toBe(400)
expect(result.headers)
});

Timeout error always occurs when tried to test firestore security rules with emulator

I tried to test firestore security rules with emulator, but timeout error always occur...
Please let me know if you have the same phenomenon or have a solution.
Test result
Start emulator
% firebase serve --only firestore
Run test
% yarn test
yarn run v1.19.2
$ jest
FAIL tests/firestore.test.ts (7.123s)
Firestore Security Rule
✕ sample1 (5044ms)
● Firestore Security Rule › sample1
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
30 | });
31 |
> 32 | test("sample1", async () => {
| ^
33 | const db = createAuthApp();
34 | const user = usersRef(db).doc("test");
35 | await firebase.assertSucceeds(user.set({ name: "John" }));
at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
at Suite.<anonymous> (tests/firestore.test.ts:32:3)
at Object.<anonymous> (tests/firestore.test.ts:16:1)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 8.038s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Environment
※Firestore security rule is allowing everything because of sample
■firestore.test.ts
import * as firebase from "#firebase/testing";
import * as fs from "fs";
const PROJECT_ID = "firestore-rule-test";
const RULES_PATH = "firestore.rules";
// Create Firestore app with auth
const createAuthApp = (auth?: object): firebase.firestore.Firestore => {
return firebase
.initializeTestApp({ projectId: RULES_PATH, auth: auth })
.firestore();
};
const usersRef = (db: firebase.firestore.Firestore) => db.collection("user");
describe("Firestore Security Rule", () => {
beforeAll(async () => {
await firebase.loadFirestoreRules({
projectId: PROJECT_ID,
rules: fs.readFileSync(RULES_PATH, "utf8")
});
});
afterEach(async () => {
await firebase.clearFirestoreData({ projectId: PROJECT_ID });
});
afterAll(async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
});
test("sample1", async () => {
const db = createAuthApp();
const user = usersRef(db).doc("test");
await firebase.assertSucceeds(user.set({ name: "John" }));
await firebase.assertSucceeds(user.get());
});
});;
In your createAuthApp() function, you are initialising the test application with a project ID of RULES_PATH but in your tests you are loading security rules using a project ID of PROJECT_ID.
Changing
.initializeTestApp({ projectId: RULES_PATH, auth: auth })
to
.initializeTestApp({ projectId: PROJECT_ID, auth: auth })
should fix your issue.
In case that it doesn't solve the issue, you can change the Jest timeout to more than 5 seconds using --testTimeout=<number of ms> to give the test more time to complete.
Lastly, for clarity, consider renaming createAuthApp to createFirestoreInstance as "create Auth" implies something to do with the FirebaseAuth class.

Categories

Resources