How to mock a variable inside a Jest test function? - javascript

I'm testing the Express application with Jest and came across a slight problem - the module uses variable that is initialized before the test execution, here is my app.js file:
const app = express();
const isDev = process.env.NODE_ENV === 'development';
app.get('*', (req, res, next) => {
if (isDev) {
res.status(404).json({ error: 'Wrong URL' });
} else {
res.sendFile(path.join(__dirname, '../index.html'));
}
});
app.use(errorHandler);
module.exports = app;
When I run Jest tests, my process.env.NODE_ENV is equal to test, that's why I can't cover the first if condition, where isDev is true.
I've tried to reassign process.env.NODE_ENV before the test request - it works, but as isDev variable initialization has been done before test execution, it didn't work.
This is my test:
const request = require('supertest');
const app = require('../app');
describe('GET /*', () => {
const OLD_ENV = process.env;
beforeEach(() => {
// Clear JEST cache
jest.resetModules();
process.env = { ...OLD_ENV };
Reflect.deleteProperty(process.env, 'NODE_ENV');
});
test('Not existing path (development env) - 404 status', async () => {
process.env.NODE_ENV = 'development';
const response = await request(app).
get('/wrongUrl');
expect(response.status).toBe(404);
});
});
How can I mock the isDev variable inside my test?

you can use jest.isolateModules(fn) to app in isolation like this:
describe("GET /*", () => {
describe("on development", () => {
let app;
beforeAll(() => {
process.env.NODE_ENV = "development";
jest.isolateModules(() => {
app = require("../app");
});
});
it("should to this", () => {
expect(app).....
});
});
describe("on production", () => {
let app;
beforeAll(() => {
process.env.NODE_ENV = "production";
jest.isolateModules(() => {
app = require("../app");
});
});
it("should to that", () => {
expect(app())....
});
});
});

You could create an .env file just for testing.
If you are using express you could also use dotenv.
With this package you can import env variables from different files.
Just add this line at the top of your file.
require('dotenv').config({ path: process.cwd() + '/path/to/test.env' });
This way you can always change the env variables you want to use, before every test.

Related

How do I unit test an API mocking PrismaClient when the API is written in CommonJS?

I am testing a nodejs express API with a PG db wrapped with Prisma ORM
I configured a testing singleton instance as described by Prisma docs
Since the API is implemented in CommonJS and not TS, I had to make some changes as described in this beautiful page.
Here is a synthesis of what I did, will try to make it short, so it's easier to read
orgs.js (A GET route served by the mock server later on ...)
const Router = require('express-promise-router')
const router = new Router()
const PrismaPool = require('../db/PrismaPool');
module.exports = router
router.get('/assessments', async (req, res) => {
try{
const prisma = PrismaPool.getInstance();
const data = await prisma.org.findUnique({
select:{
assessments:true,
},
where: {
id: res.locals.orgId,
},
})
res.send(data)
}
catch(err){
handleError(err, "[GET]/orgs/assessments", 400, req, res)
}
})
PrismaPool.js (A wrapper to access the unique prisma client instance)
const prisma = require('./PrismaClientInstance').default
class PrismaPool {
constructor() {
throw new Error('Use PrismaPool.getInstance()');
}
static getInstance() {
return prisma
}
}
module.exports = PrismaPool;
PrismaClientInstance.js The unique instance of PrismaClient class. This is the tricky part stitching between the CommonJS world and the TS world.
'use strict';
exports.__esModule = true;
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
exports['default'] = prisma;
All this configuration works GREAT at runtime, now, when wrapping it with JEST in unit tests, things go south quickly ...
mock_server.js (a simplified server to expose the orgs API above)
const http = require('http');
const express = require('express');
var orgsRouter = require('../orgs');
const app = express();
app.use('/orgs', orgsRouter);
const port = 3011
app.set('port', port);
const server = http.createServer(app);
function onError(error) {
// herror handling
}
function onListening() {
// some debug messages
}
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
module.exports = server
PrismaSingletonForTesting.ts (A jest deep mock of the PrismaClient instance)
import { PrismaClient } from '#prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'
import prisma from './PrismaClientInstance'
jest.mock('./PrismaClientInstance', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}))
beforeEach(() => {
mockReset(prismaMock)
})
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
orgs.test.js (The tests of the orgs API)
const Request = require("request")
const { prismaMock } = require('../../db/PrismaSingletonForTesting')
const TEST_ORGID = 1
describe('egrest server', () => {
let server
beforeAll(() => {
server = require('./test_server')
})
afterAll(() => {
server.close()
})
describe('assessments', () => {
let data = {}
beforeAll(() => {
const testorg = {
id: TEST_ORGID,
name: 'jestest',
admin:33,
avail_tests: 1234
}
prismaMock.org.findUnique.mockResolvedValue(testorg)
})
it(`read remaining assessments for org ${TEST_ORGID}`, (done) => {
Request.get("http://localhost:3011/orgs/assessments", (error, response, body) => {
data.status = response.statusCode
data.body = body
data.error = error
console.dir(body)
done()
})
})
})
})
I also configured .jest.config with the required line setupFilesAfterEnv: ['./db/PrismaSingletonForTesting.ts']
When I run this test, I get data=undefined in orgs.js, even-though I mocked prisma.org.findUnique by doing prismaMock.org.findUnique.mockResolvedValue(testorg) as described by prisma docs.
Any help would be appreciated.

deploy multiple react projects on a single domain

currently, it's working using hash routing, I want to remove hash for SEO reasons so it is possible?
it creates routes like
https://mainDomain/program ← program build run
https://mainDomain/program/#detail ← program routing
I want https://mainDomain/program/#detail to https://mainDomain/program/detail
if I use BrowserRouter it shows no such directory
this is my build deploy structure in AWS ↓
might be this Helpful for you. you can create one Node server which can serve your Project builds.
let path = require("path");
let fsp = require("fs/promises");
let express = require("express");
let isProduction = process.env.NODE_ENV === "production";
async function createServer() {
let app = express();
/**
* #type {import("vite").ViteDevServer}
*/
let vite;
if (!isProduction) {
vite = await require("vite").createServer({
root: process.cwd(),
server: { middlewareMode: "ssr" },
});
app.use(vite.middlewares);
} else {
app.use(require("compression")());
app.use(express.static(path.join(__dirname, "dist")));
}
app.use("*", async (req, res) => {
let url = req.originalUrl;
// Use a separate HTML file for the "Inbox" app.
let appDirectory = url.startsWith("/inbox") ? "inbox" : "";
let htmlFileToLoad;
if (isProduction) {
htmlFileToLoad = path.join("dist", appDirectory, "index.html");
} else {
htmlFileToLoad = path.join(appDirectory, "index.html");
}
try {
let html = await fsp.readFile(
path.join(__dirname, htmlFileToLoad),
"utf8"
);
if (!isProduction) {
html = await vite.transformIndexHtml(req.url, html);
}
res.setHeader("Content-Type", "text/html");
return res.status(200).end(html);
} catch (error) {
if (!isProduction) vite.ssrFixStacktrace(error);
console.log(error.stack);
return res.status(500).end(error.stack);
}
});
return app;
}
createServer().then((app) => {
app.listen(3000, () => {
console.log("HTTP server is running at http://localhost:3000");
});
});
for extra information, you can referlink.

How Can I Serve Static Content Alongside Dynamic Routes in A Deno Oak Server

I am used to working with NodeJS and Koa. I've been playing with Deno and have run the example of a static fileserver:
/* static_server.js */
import { Application } from 'https://deno.land/x/oak/mod.ts'
const port = 8080
const app = new Application()
// Error handler middleware
app.use(async (context, next) => {
try {
await next()
} catch (err) {
console.error(err)
}
})
// Send static content
app.use(async (context) => {
console.log(`${context.request.method} ${context.request.url.pathname}`)
await context.send({
root: `${Deno.cwd()}/static`,
index: "index.html",
})
})
await app.listen({ port })
I have also created a dynamic server using routes:
/* routes.js */
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
const port = 8080
const app = new Application()
const router = new Router()
router.get('/', context => {
context.response.body = 'Hello world!'
})
router.get('/foo', context => {
context.response.body = 'Book Page'
})
router.get('/foo/:thing', context => {
context.response.body = `Foo ${context.params.thing}`
})
app.use(router.routes())
app.use(router.allowedMethods())
await app.listen({ port })
How can I combine these so that I can serve dynamic content but also provide static files such as the stylesheet?
In my Koa code I use the koa-static package:
import serve from 'koa-static'
app.use(serve('public'))
What is the equivalent for an Oak server?
Adding suggested code (thanks Jonas Wilms)
/* static_content.js */
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
const port = 8080
const app = new Application()
const router = new Router()
router.get('/', context => {
context.response.body = 'Hello world!'
})
router.get('/foo', context => {
context.response.body = 'Book Page'
})
router.get('/foo/:thing', context => {
context.response.body = `Foo ${context.params.thing}`
})
router.get(context => context.send({ root: `${Deno.cwd()}/static` }))
app.use(router.routes())
app.use(router.allowedMethods())
await app.listen({ port })
but this still does not work...
After combining a lot of the information in the comments I managed to get things working:
/* static_content.js */
import { Application, Router, Status } from 'https://deno.land/x/oak/mod.ts'
const port = 8080
const app = new Application()
const router = new Router()
// error handler
app.use(async (context, next) => {
try {
await next()
} catch (err) {
console.log(err)
}
})
// the routes defined here
router.get('/', context => {
context.response.body = 'Hello world!'
})
router.get('/error', context => {
throw new Error('an error has been thrown')
})
app.use(router.routes())
app.use(router.allowedMethods())
// static content
app.use(async (context, next) => {
const root = `${Deno.cwd()}/static`
try {
await context.send({ root })
} catch {
next()
}
})
// page not found
app.use( async context => {
context.response.status = Status.NotFound
context.response.body = `"${context.request.url}" not found`
})
app.addEventListener("listen", ({ port }) => console.log(`listening on port: ${port}`) )
await app.listen({ port })
I know I'm a bit late on the thread, but there are some things I would like to point out.
In Oak 10.1 (the current version at the time of this writing), the send function throws an error if the file it tired to load did not exist. Thus, our static+dynamic server can take on the following form.
import { oak, pathUtils } from './deps.ts'
const app = new oak.Application()
const router = new oak.Router()
app.use(async (ctx, next) => {
try {
await oak.send(ctx, ctx.request.url.pathname, {
root: 'static',
index: 'index.html',
})
} catch (_) {
await next()
}
})
router.get('/dynamic', ctx => {
ctx.response.body = 'dynamic route worked'
})
app.use(router.allowedMethods())
app.use(router.routes())
app.listen({ port: 8000 })
If you want to serve your static files at a certain root path, change the static middleware so that it checks for the root and then omits that root path from the second argument of the send function.
function staticMiddleware(rootPath: string, staticDirectory: string) {
return async (ctx, next) => {
if (!ctx.request.url.pathname.startsWith(rootPath)) return await next()
const newPath = ctx.request.url.pathname.slice(rootPath.length)
if (!newPath.startsWith('/') && newPath.length) return await next()
try {
await oak.send(ctx, newPath, {
root: staticDirectory,
index: 'index.html',
})
} catch (_) {
await next()
}
}
}
app.use(staticMiddleware('/assets', 'static'))
I think you should use the static router at last. Because when use static server first, dynamic router is nonreachable for static router error.
app.use(router.routes())
app.use(router.allowedMethods())
// move the static router down
app.use( async context => {
context.response.status = Status.NotFound
context.response.body = `"${context.request.url}" not found`
})
Not sure whether this is still relevant or already outdated, but as of now (August 2022), there seems to be no general answer to this.
Serving Static Files Alongside Your API Using Oak/Deno
When setting up OpenAPI for an oak-based REST service, I was coming across this issue as well. Requirements were:
Serve openapi.yml statically from /openapi/openapi.yml
Serve a HTML statically from /openapi for convenience
Serve prefixed routers unaffectedly
A straight-forward approach to serve static files from a certain directory under a sub-path of the application is using a middleware and checking the path:
import {
Application, Context, Router
} from 'https://deno.land/x/oak#v11.1.0/mod.ts';
const app = new Application();
const port = 3000;
// Middleware only hooking in and sending static files if prefix matches
// the desired subpath:
const openapi = async (ctx: Context, next: () => Promise<unknown>) => {
const prefix = '/openapi'; // Sub-path to react on
if (ctx.request.url.pathname.startsWith(prefix)) {
await ctx.send({
root: `${Deno.cwd()}/src/openapi/`, // Local directory to serve from
index: 'index.html',
path: ctx.request.url.pathname.replace(prefix, ''), // Map to target path
});
} else {
// If the request does not match the prefix, just continue serving from
// whatever comes next..
await next();
}
};
// This is a dummy API endpoint wrapped into a prefixed router for demo:
const statusRouter = new Router({ prefix: '/status' });
statusRouter.get('/', (ctx: Context) => {
ctx.response.body = {
healthy: true,
ready: true,
};
});
// Boilerplate..
app.use(openapi);
app.use(statusRouter.routes());
app.use(statusRouter.allowedMethods());
app.addEventListener('listen', () => {
console.log(`Listening on localhost:${port}`);
});
await app.listen({ port });
Running this MWE using deno run --allow-net --allow-env --allow-read src/serve.ts, you'll
find the statically served /openapi/openapi.yml,
find the index.html from your local static path served under /openapi (resp. /openapi/ and /openapi/index.html)
find the /status API behaving just normally.
i'm using like that. In html you can provide a path to your file:
<script src="/js/app.js"></script>
then you can use routes to provide what do you want to use on path js/app.js:
import {RouterContext} from 'https://deno.land/x/oak/mod.ts'
const decoder = new TextDecoder("utf-8")// set doecoder
const fileCont = await Deno.readFile('./views/test.js') //getting file conetent
const fileJS = decoder.decode(fileCont) //decoding it
router.get('/js/app.js', (ctx: RouterContext) => { //yep, route can has defferents of real file location
ctx.response.type = "application/javascript"
ctx.response.body = fileJS
})
and whatever you are providing this link somewhere it'll render you file.
Deno REST API

How can I test express server with supertest in next.js?

I have built my portfolio webpage with next.js now I need to test it. to test the express server I use supertest. But the problem is I need to refactor express to use it. Because supertest need to access to app() before listening.
I started the way how I used to implement in node.js app. Put the express code in app.js and call it in index.js.
const express = require("express");
const server = express();
const authService = require("./services/auth");
const bodyParser = require("body-parser");
//put all the middlewares here
module.exports = server;
and then in index.js
const server = require("express")();
// const { parse } = require("url");
const next = require("next");
const routes = require("../routes");
const path = require("path");
require("./mongodb");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
// const handle = app.getRequestHandler(); //this is built in next route handler
const handle = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = require("./app");
//I required this outside too but it did not solve the issue
server.listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
with this set up, express is listening, I am able connect to mongodb, during the start up there is no issue.
When i request to localhost:3000, there is no response from localhost, it is spinning till timeout
Create a test client:
// test-client.ts
import { createServer, RequestListener } from "http";
import { NextApiHandler } from "next";
import { apiResolver } from "next/dist/next-server/server/api-utils";
import request from "supertest";
export const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
undefined,
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
Test your APIs with:
// user.test.ts
import viewerApiHandler from "../api/user";
import { testClient } from "../utils/test-client";
const request = testClient(viewerApiHandler);
describe("/user", () => {
it("should return current user", async () => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.body).toStrictEqual({ name: "Jane Doe" });
});
});
For those who want to add query parameters, here's the answer:
import { createServer, RequestListener } from 'http'
import { NextApiHandler } from 'next'
import { apiResolver } from 'next/dist/server/api-utils/node'
import request from 'supertest'
export const handlerRequester = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
let query = {}
let queryUrl = req.url.split('?')[1]
if (queryUrl) {
queryUrl
.split('&')
.map((p) => [p.split('=')[0], p.split('=')[1]])
.forEach((k) => {
query[k[0]] = k[1]
})
}
return apiResolver(
req,
res,
query,
handler,
{
previewModeEncryptionKey: '',
previewModeId: '',
previewModeSigningKey: '',
},
false
)
}
const server = createServer(listener)
return [request(server), server]
}
I've just released a new npm package which handle this case here:
https://www.npmjs.com/package/nextjs-http-supertest
Feel free to test it and give me feedback !

How to add backend routes with hapi-nuxt in a Nuxt.js project?

I'm using hapi-nuxt in a javascript project similar to a Nuxt tutorial I am watching. I generated the skeleton app using:
npx create-nuxt-app <project-name>
That gives me the following code in server/index.js:
const Hapi = require('hapi')
const consola = require('consola')
const HapiNuxt = require('hapi-nuxt')
const server = new Hapi.Server({
host: process.env.HOST || 'localhost',
port: process.env.PORT || 3000
})
server
.register({
plugin: HapiNuxt
})
.then(() => server.start())
.then(() =>
consola.ready({
message: `Server running at: ${server.info.uri}`,
badge: true
})
)
.catch(err => {
consola.error(err)
throw err
})
Now I want to add the routes listed in server/routes/index.js. I believe the code is similar to:
const routes = require('./routes');
...
routes.forEach(route => {
app.route(route);
}
Assuming that code is correct, where do I put it?
Here is an example
// server/index.js
const consola = require('consola')
const Hapi = require('#hapi/hapi')
const HapiNuxt = require('#nuxtjs/hapi')
const Routes = require('./api')
async function start() {
const server = new Hapi.Server({
host: process.env.HOST || '127.0.0.1',
port: process.env.PORT || 3000
})
await server.register({
plugin: HapiNuxt,
options: {}
});
await server.route(Routes);
await server.start()
consola.ready({
message: `Server running at: ${server.info.uri}`,
badge: true
})
}
process.on('unhandledRejection', (error) => consola.error(error))
start()
// server/api/index.js
const route1 = {
path: '/api',
method: 'GET',
handler (request, h) {
return {
works: true
}
}
}
module.exports = [
route1
]

Categories

Resources