Iam using Inversify & inversify-express-utils for building an Express Server. I am new to testing(Jest).
I need to call API instead of calling the controller class so that I can test the AuthGuard and RolesGuard. I tried using supertest library. Iam facing some issues. Im pretty sure that I have a wrong test setup.
user.controller.spec.ts:
import 'reflect-metadata';
import { mock } from 'jest-mock-extended';
import request from 'supertest';
import { MailService } from '../../../shared/mail/mail. Service';
import { UserController } from '../controller/users.controller';
import { UserService } from '../service/users.service';
import { ValidationException } from '../../../core/exception';
import { createUserFixture, updateUserFixture, userFixture } from '../fixtures/userFixture';
import { bootstrap } from '../../../main';
const userServiceMock = mock<UserService>();
const mailServiceMock = mock<MailService>();
describe('Users Controller', () => {
let app: any;
let sut: UserController;
sut = new UserController(userServiceMock, mailServiceMock);
beforeEach(async () => {
app = await bootstrap();
});
test('should throw auth error', async () => {
const req = await request(app).get('/user');
console.log(req);
});
});
main.ts
import 'reflect-metadata';
import cors from 'cors';
import express, { NextFunction, Response, Request } from 'express';
import helmet from 'helmet';
import { InversifyExpressServer } from 'inversify-express-utils';
import { container } from './core/inversify/inversify.config';
import { Sql } from './database/sql';
import { GlobalErrorConfig } from './core/server';
export const bootstrap = () => new Promise((resolve) => {
/// /Connect to SQL Server
new Sql().connect();
/// Start Server
const server = new InversifyExpressServer(container);
server.setConfig((app) => {
app.use(express.json());
app.use(cors());
app.use(helmet());
});
/// Global Error Config
GlobalErrorConfig(server);
/// Build Server
const app = server.build();
app.listen(process.env.PORT || 5000, () => {
console.log(
`Server is running on http://localhost:${process.env.PORT || 5000}`,
);
resolve(app);
});
});
bootstrap();
While trying to start the test, I get these following error:
The error:
Jest trying to warn delayed logs.
Same controller name error(app working normally on npm start).
Jest warning a file import after Jest environment torn down.
I have no clue what went wrong. I would really appreciate if anyone points out the issue or suggesting me the proper Jest API testing setup. Thanks in advance.
Related
I have a simple Express application that exposes a RESTful API. It uses Knex and Objection to access the database, and Jest / Supertest for testing the API.
I have a test that starts the server, fetches the available data from a given route and asserts the received values. Everything works fine, except that Jest never exits after executing this test.
This is my test:
import { app } from "../../src/application.js";
import { describe, expect, test } from "#jest/globals";
import request from "supertest";
describe("Customer Handler", () => {
test("should retrieve all existing customer(s)", async () => {
const expected = [
// ...real values here; omitted for brevity
];
const response = await request(app).get(`/api/customers`);
expect(response.statusCode).toStrictEqual(200);
expect(response.headers["content-type"]).toContain("application/json");
expect(response.body).toStrictEqual(expected);
});
});
The application.js file looks very much like a usual Express setup/configuration file:
import { CustomerHandler } from "./handler/customer.handler.js";
import connection from "../knexfile.js";
import express from "express";
import Knex from "knex";
import { Model } from "objection";
const app = express();
Model.knex(Knex(connection[process.env.NODE_ENV]));
// ...removed helmet and some other config for brevity
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/api/customers", CustomerHandler);
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
errors: {
error: err,
message: err.message,
},
});
next();
});
export { app };
I've tried --detectOpenHandles, but nothing else is printed in the console, so I can't see any hints about what the issue might be — which I suspect it's Knex / Objection because I'm using SQLite, so probably the connection to the database is not getting closed.
In the meantime I'm using --forceExit, but I would like to figure it out why is Jest not able to exit.
OK, looks like I was on the right track for this one.
Refactoring application.js to export the Knex object (after it's configured), and calling knex.destroy() after the test passes is the solution for this setup.
// application.js
...
const knex = Knex(connection[process.env.NODE_ENV]);
Model.knex(knex);
...
export { app, knex };
...then in the test(s), make sure you import knex and call destroy():
// customer.handler.test.js
import { app, knex } from "../../src/application.js";
import { afterAll, describe, expect, test } from "#jest/globals";
import request from "supertest";
describe("Customer Handler", () => {
afterAll(async () => {
await knex.destroy();
});
test("should retrieve all existing customer(s)", async () => {
const expected = [ /* ...real values here */ ];
...
expect(response.body).toStrictEqual(expected);
});
});
I am using MSW for my React app and am getting the following error when trying to run in mock mode;
Error occurred while trying to proxy request /api/myApi from localhost:3000 to { port: 8080 } (ECONNREFUSED) (https://nodejs.org/api/errors.html#errors_common_system_errors)
I am trying to use both for testing and as dev api server (mock data)
Not sure if there is additional config needed for proxying the request when running in mock mode.
Below is my command in package.json
"start:mock": "cross-env NODE_ENV=mock webpack serve --progress --config config/webpack.local.js",
Below is the code in src/index.js
import * as serviceWorker from './serviceWorker';
if (process.env.NODE_ENV === 'mock') {
const { worker } = require('./browser')
worker.start()
}
//src/browser.js
import { setupWorker } from 'msw'
import { handlers } from './testServer'
// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers)
Below is src/testServer.js
import "whatwg-fetch";
import { rest } from "msw";
import { setupServer } from "msw/node";
import data from "./data";
const handlers = [
rest.get("http://localhost/api/myApi", (req, res, ctx) => {
return res(ctx.status(200), ctx.json(data));
})
];
// This configures a request mocking server with the given request handlers.
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
export { server, rest, handlers};
it's the first time that i post on forums,
I really needs your help.
I'm stuck with a problem, I have an Ionic/React application, a Node.js application and a graphQL/Apollo API,
when i'm calling the graphql API from my browser it's all working fine but when i'm building the app with capacitor and running it on my Android device, I get "Network error: Failed to fetch".
Here is my client.ts code where i'm setting up my ApolloClient
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory';
class RequestUtils {
clientApollo: any;
constructor() {
const httpLink = createHttpLink({
uri: 'http://192.168.1.86:4000/graphql'
});
const authLink = setContext((req, { headers }) => ({
headers: {
...headers
}
}));
this.clientApollo = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
}
}
const requestUtils = new RequestUtils();
const client: ApolloClient<NormalizedCacheObject> = requestUtils.clientApollo;
export default client;
and here is my graphql.ts
import resolvers from "../resolvers/index";
import { ApolloServer, AuthenticationError } from "apollo-server-express";
import { getModels } from "../models/index";
import schemas from "../schemas/index";
import * as express from "express";
import * as cors from "cors";
import { connectToMongo } from "./config";
import { info, success, warning, error } from "./logger";
export class query {
public app: any;
constructor() {
const models = getModels();
this.app = express();
this.app.use(cors());
try {
info("Starting connection to graphql");
const server = new ApolloServer({
typeDefs: schemas,
resolvers,
context: async ({ req }) => {
if (req) {
return {
models: models
};
}
}
});
this.app = express();
server.applyMiddleware({ app: this.app, path: "/graphql" });
success(`Connected to graphql`);
} catch (error) {
warning(error);
}
this.app.listen(4000, () => {
connectToMongo();
});
}
}
I think that it's a problem with something like cors or ip adress but i don't find any solution to my problem.
I hope someone can help me !
EDIT:
I tried to run my node server on another computer and cal the graphql API from my main computer with my ionic react react webapp. The origin is in fact different but there is no error, all works perfectly. But with my builded app on my android device, always same error.
So, I think it's not cors, maybe it's something with Capacitor/cordova, or something like this.
At first, i thought android app wasn't allowed to connect to network, I checked and it's connected, but I'm not sure.
If someone could help me, it would be very sympathic , I'm really stuck with this, my app is useless if i can't connect to server en database XD
Had exactly the same problem.
It looks like the cause is that Android forbids HTTP requests by default.
The following fix worked for me:
To allow http requests, I added android:usesCleartextTraffic="true" to my app's AndroidManifest.xml
Solution post
I "solved" my problem by launching my app with " ionic capacitor run android -l --external "
I don't what will happens if the app is in production but at least I can progress in my dev.
I'll see later what happens ¯_(ツ)_/¯
I'm working on a nodejs fastify based app service and using typedi for dependency injection.
Some services I use need async initialization.
MyService.ts
export class MyService {
constructor() {
}
public async init() {
....
}
}
I am trying to initialize the service at application startup so that any service doing Container.get(MyService) gets this initialized instance of MyService
app.ts
export default async function(fastify: FastifyInstance, opts: Options, next: Function) {
// This loads everything under routes
fastify.register(autoload, {
dir: path.join(__dirname, "routes"),
options: opts,
includeTypeScript: true,
});
await Container.get(MyService);
next();
}
server.ts
import app from "./app";
const server = fastify({
logger: logger
});
server.register(oas, docs);
server.register(app);
server.ready(err => {
if (err) throw err;
server.oas();
});
server.listen(config.port, (err) => {
if (err) {
server.log.error(err);
process.exit(1);
}
server.log.info(`server listening on ${server.server.address()}`);
});
export default server;
My attempt to initialize MyService is failing.
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"
Any hints to what I'm doing wrong? I'm new to nodejs and would really appreciate sample code that is correct for this scenario.
Edit
I tried import
Container.import([CmkeService]);
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"
I am trying to create a download button, which enables the user to download a document from my node.js server.
Here's the fancy button:
I am using Angular as a front-end framework and node.js and express.js for the backend.
For the front-end:
app.component.ts:
import { Component } from "#angular/core";
import { GenerateReportService } from "./services/generate-report.service";
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
constructor(private generateReportService: GenerateReportService) {}
generateReportbyGet() {
this.generateReportService.generateReportbyGet().subscribe(() => {
console.log("generateReportbyGet ...");
});
}
}
app.component.html
<button color="primary" (click)="generateReportbyGet()">Generate report By Get</button>
<router-outlet></router-outlet>
generate-report.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class GenerateReportService {
uri = 'http://localhost:5353';
constructor(private http: HttpClient) {}
generateReportbyGet() {
return this.http.get(`${this.uri}/generateReportByGet`);
}
}
In the backend I have:
server.js
const bodyParser = require('body-parser');
const cors = require('cors')
const express = require('express');
const app = express();
const router = express.Router();
const path = require('path');
const createDocumentService = require('./services/generateDocumentService.js');
app.use(cors());
app.use(bodyParser.json());
router.route('/generateReportByGet').get((req, res) => {
res.download(path.join(__dirname, 'docs/doc1.txt'), function (err) {
if (err) {
console.log(err);
console.log('res.headersSent: ', res.headersSent);
} else {
console.log('NO ERROR');
console.log('res.headersSent: ', res.headersSent);
}
});
});
app.use('/', router);
app.listen(5353, () => console.log('Express server running on port 5353'));
doc1.txt
When I click on my fancy button, I get this error:
error: {error: SyntaxError: Unexpected token T in JSON at position 0
at JSON.parse () at XMLHtt…, text: "This is the text inside
doc1.txt that I want the user to be able to download."}
headers:
HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure during parsing for
http://localhost:5353/generateReportByGet" name: "HttpErrorResponse"
I have tested the backend code separately by writing
localhost::5353/generateReportByGet
in the address bar. And the file gets downloaded successfully.
Also what's strange is that
res.download()
doesn't give any errors.
So here is my question:
1/ What is causing that error, I get on the browser console?
2/ Is using res.download() the wrong way to do this?
Thank you!!
You need to specify the response type when you make the request. Angular will otherwise expect the incoming data to be json and try to parse it.
return this.http.get(`${this.uri}/generateReportByGet`, {responseType: 'text'});
You need to handle res.download on client side by setting responseType.
Modify GET request in generate-report.service.ts as:
generateReportbyGet() {
return this.http.get(`${this.uri}/generateReportByGet`, {responseType: ResponseContentType.Blob})
.map((response: Response)=> response.blob());
}
In app.component.ts modify generateReportbyGet as:
generateReportbyGet() {
this.generateReportService.generateReportbyGet().subscribe((data) => {
console.log("generateReportbyGet ...");
// save blob in file
});
}