Unit Testing the NestJS Routes are defined - testing

I found this question about determine the routes. While the first answer is exactly what I need, and it works
import { Controller, Get, Request } from "#nestjs/common";
import { Request as ExpressRequest, Router } from "express";
#Get()
root(#Request() req: ExpressRequest) {
const router = req.app._router as Router;
return {
routes: router.stack
.map(layer => {
if(layer.route) {
const path = layer.route?.path;
const method = layer.route?.stack[0].method;
return `${method.toUpperCase()} ${path}`
}
})
.filter(item => item !== undefined)
}
}
I want to be able to unit test this.
My end to end test works fine
it('/api (GET) test expected routes', async done => {
const ResponseData = await request(app.getHttpServer())
.get('/api')
.set('Accept', 'application/json');
expect(ResponseData.status).toBe(200);
expect(ResponseData.headers['content-type']).toContain('json');
expect(ResponseData.body.routes.length).toBeGreaterThan(2);
done(); // Call this to finish the test
});
The problem I am having, is how to create and pass the Request part that is needed for the root() call for a unit test. The ExpressRequest is not a class or anything to simply create, and then assign values. It is currently a large definition. I assume there must be an easy way to create one, but I have not found it yet.

You can make use of the #golevelup/ts-jest package to help create mocks of objects. It can take an interface as a generic and return an entire jest mock that is compatible with the type.

Related

Redefine $fetch in nuxt3 with global onRequest handler

Is it possible to use global onRequest handler to $fetch with Nuxt3, to add specific data on each request?
With nuxt2 and axios it was simple
/plugins/axios.js
export default function ({ $axios, store, req }) {
$axios.onRequest((config) => {
if (config.data) {
config.data.test = '123';
} else {
config.data = { test: '123' };
}
return config;
});
}
But how achieve same goal on Nuxt3 and $fetch?
Ok, so Nuxt3 $fetch documentation says:
Nuxt uses ofetch to expose globally the $fetch helper...
When we jump into ofetch documentation we can see the Interceptors section. This gives us some options to do what you are trying to achieve. My suggestion is this:
Create a http composable (or anyother name you wish):
// composables/use-http.js
const opts = {
async onRequest({ request, options }) {
// Add your specific data here
options.query = { t: '1234' }
options.headers = { 'Authorization': 'my_token' }
}
}
export default () => $fetch.create(opts)
And here we are making usage of the onRequest interceptor from ofetch
onRequest is called as soon as ofetch is being called, allowing to modify options or just do simple logging.
There you can add any data you want, if you need you can create the logic to pass parameters to this composable and so on...
Now, to actually fetch the data (use the composable):
const http = useHttp() // useHttp is auto-imported
const data = await http('/url') // will trigger the interceptor

express-validator on PUT methods

I'm creating an API and decided to use express-validator for validation (duh), I've never used this before so I'm unsure on some aspects of it so my validations might not be the best but I'm getting by.
I have built two validation middle ware using this and export them from the same folder like this:
module.exports = {
create: require('./create'),
update: require('./update')
}
So I can then do this in my router:
const validation = require('../validations/plotValidation')
// ...
router.get('/', controller.all)
router.post('/create', validation.create(), controller.create)
router.get('/:plotId', controller.read)
router.put('/:plotId/update', validation.update(), controller.update)
router.delete('/:plotId/delete', controller.delete)
// ...
I'm not good enough with express-validator to do both validate both routes with the same file, maybe I'll try it at some point, anyway.
The .post method works fine and validates everything I want it to however the .put method just seems to be ignoring every check here are is file in case you want to see the checks:
const { body, check, param } = require('express-validator');
module.exports = () => {
return [
param('plotId')
.exists().withMessage('URI requires plot id'),
body('price')
.optional()
.isObject()
]
}
As you can probably tell I only just stated it, but even with only these two tiny checks it just doesn't seem to run.
Does express-validator not work on PUT methods?
For anyone else who has this issue I solved this by using .run on my checks, you can read more about this here essentially this is the code that saved me:
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
res.status(400).json({ errors: errors.array() });
};
};

Computed params with skip/enable

I was getting annoyed that some of my apollo requests were working and some were not. The ones that don't seem to work are requests with computed params.
Here is an example of one that does work:
import { computed } from "#vue/composition-api";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
import { useGraphQuery } from "./graph-query";
export function useGetCategory(context) {
const params = computed(() => {
const slug = context.root.$route.params.categorySlug;
if (!slug) return;
return { slug };
});
const { response, error, loading } = useGraphQuery(
params,
getCategoryBySlug,
(data) => data.categoryBySlug
);
return { category: response, categoryError: error, categoryLoading: loading };
}
As I am computing my params on the categorySlug, it is available on the route, so it should never be null/undefined.
My useGraphQuery method looks like this:
import { useQuery, useResult } from "#vue/apollo-composable";
export function useGraphQuery(params, gql, pathFn, clientId = "apiClient") {
// if (!params?.value)
// return {
// response: ref(undefined),
// loading: ref(false),
// error: ref(undefined),
// query: ref(undefined),
// };
// TODO: figure our a way to skip the call if the parameters are null
const { result, loading, error, query, fetchMore } = useQuery(gql, params, {
clientId,
//enabled: !!params?.value,
});
const response = useResult(result, null, pathFn);
return { response, loading, error, query, fetchMore };
}
As you can see, I am having an issue because I can't skip and enabled doesn't seem to work as a suitable workaround (for skip).
I tried to return a reference if the parameters are null/undefined, but this never tried to execute the query if the computed params became available.
So my question is how can I skip the request or wait until the params are available?
You should consider changing the flow that calls this method, so it would be called only when the params are defined, instead of trying to skip it when not ready and try to retrigger it again from within the method. Most of the time this will make the code clearer, and also make it more resource-efficient as it won't make unneeded method calls.
If you depend on user input try to validate the input is ready before calling this method.
You can also add a watcher on the params that will trigger the flow when they change, and check in the watcher that all the relevant values are defined before calling the method.
Of course, if you can use computed variables it is better than using watchers in most cases, but in some cases, it can help (mostly when the variable is calculated by an async function, for example, the use of apollo request).

Testing axios interceptors with jest

I am using an axios interceptor to add authorization token to its header. The interceptor works fine.
//api.js
import { getAccessToken } from "./utils";
const apiInstance = axios.create();
apiInstance.interceptors.request.use((configIns) => {
const token = getAccessToken();
configIns.headers.Authorization = token ? `Bearer ${token}` : "";
return configIns;
});
export { apiInstance };
Here is my test file to test the interceptor.
// api.test.js
import { apiInstance } from "./api"
import {getAccessToken} from "./utils";
describe("request interceptor", () => {
it("API request should add authorization token to header", () => {
const getAccessToken = jest.fn(getAccessToken);
getAccessTokenMock.mockReturnValue("token");
const result = apiInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
expect(getAccessTokenMock.mock.calls.length).toBe(1);
expect(result.headers).toHaveProperty("Authorization");
});
});
However the getAccessToken function in not getting mocked for some reason inside the interceptor.
The test fails.
That's not how mocks in Jest work. By calling (I assume variable should be getAccessTokenMock, not getAccessToken):
const getAccessTokenMock = jest.fn(getAccessToken);
getAccessTokenMock.mockReturnValue("token");
What you do is: you create new local mock, that when called, would call your getAccessToken function. Then you mock return value. However, your getAccessTokenMock is never called, because it's not the same instance as in your implementation!
What you need to do is mock your actual function getAccessToken from your ./utils file. It can be done, for example, like this:
import { apiInstance } from "./api"
import {getAccessToken} from "./utils";
// Mock here:
jest.mock('./utils', () => ({
getAccessToken: jest.fn(() => 'token')
});
describe("request interceptor", () => {
it("API request should add authorization token to header", () => {
const result = apiInstance.interceptors.request.handlers[0].fulfilled({ headers: {} });
expect(getAccessToken.mock.calls.length).toBe(1);
expect(result.headers).toHaveProperty("Authorization");
});
});
What is happening here is that when file is loaded, jest.mock would run first and would replace your implementation of getAccessToken with the mock - it would be then accessible from both implementation and test (as the same instance), meaning that you can verify if it has been called.
Please find more about mocks here or here. Alternatively you can also use spyOn to achieve similar result (then you don't need jest.mock, but you have to import your getAccessToken function without destructuring).

Mocking runtime config value with sinon

I am adding some config values before hapi server start. Application is works fine although in test I can not use config.get(). I can get around with proxyquire. So I was wondering
Is adding config file "dynamically" is bad design?
Is there a way I can use config.get() in such suitation?
Any alternative approach?
//initialize.js
const config = require('config');
async function startServe() {
const someConfigVal = await callAPIToGetSomeJSONObject();
config.dynamicValue = someConfigVal;
server.start();
}
//doSomething.js
const config = require('config');
function doesWork() {
const valFromConfig = config.dynamicValue.X;
// In test I can use proxiquire by creating config object
...
}
function doesNotWork() {
const valFromConfig = config.get('dynamicValue.X');
// Does not work with sinon mocking as this value does not exist in config when test run.
// sinon.stub(config, 'get').withArgs('dynamicValue.X').returns(someVal);
.....
}
Context: testing.
Is adding config file "dynamically" is bad design? => No. I have done it before. The test code changes configuration file: default.json in mid test to check whether function under test behaves as expected. I used several config utilities.
Is there a way I can use config.get() in such suitation? => Yes. For sinon usage, see the example below, which use mocha. You need to define the stub/mock before the function under test use it, and do not forget to restore the stub/mock. Also there is official documentation related to this: Altering configuration values for testing at runtime, but not using sinon.
const config = require('config');
const sinon = require('sinon');
const { expect } = require('chai');
// Example: simple function under test.
function other() {
const valFromConfig = config.get('dynamicValue.X');
return valFromConfig;
}
describe('Config', function () {
it ('without stub or mock.', function () {
// Config dynamicValue.X is not exist.
// Expect to throw error.
try {
other();
expect.fail('expect never get here');
} catch (error) {
expect(error.message).to.equal('Configuration property "dynamicValue.X" is not defined');
}
});
it('get using stub.', function () {
// Create stub.
const stubConfigGet = sinon.stub(config, 'get');
stubConfigGet.withArgs('dynamicValue.X').returns(false);
// Call get.
const test = other();
// Validate te result.
expect(test).to.equal(false);
expect(stubConfigGet.calledOnce).to.equal(true);
// Restore stub.
stubConfigGet.restore();
});
it('get using mock.', function () {
// Create mock.
const mockConfig = sinon.mock(config);
mockConfig.expects('get').once().withArgs('dynamicValue.X').returns(false);
// Call get.
const test = other();
// Validate te result.
expect(test).to.equal(false);
// Restore mock.
expect(mockConfig.verify()).to.equal(true);
});
});
Hope this helps.