I have the following code:
async save(id: string) {
const person = await PersonModel.findOne({
where: { id: id },
});
if (!person) {
await PersonModel.create({
id: '2345',
name: 'John Doe',
age: 25
});
return;
}
await person.increment({ age: 15 });
}
Now, I wanted to test person.increment() in which the age will be added with 15. I have the following code to escape the condition that will create a new record for the model.
const findOneFake = sinon.spy(() => {
return {}; //returns empty object or true
});
const proxy = (proxyquire('./path/to/file.ts', {
'./path/to/PersonModel.ts': {
default: {
findOne: findOneFake
}
}
})).default;
beforeEach(async () => {
await save();
});
it('should increment age with 15');
How am I going to do that? What do I do to test it? I can use sinon.fake() to PersonModel.create or PersonModel.update but I am troubled testing the instance of a Sequelize Model.
Related
So, I am making an e-shop app which uses Mongo DB and Express JS as the backend. I have already created the productSchema, userSchema and the categorySchema and have coded for the appropriate GET requests.
I have made a jwt.js file which handles whether the the GET request should be allowed or not based on the token.
The code for jwt.js is given below
const { expressjwt } = require("express-jwt");
function authJwt() {
const secret = process.env.secret;
const api = process.env.API_URL;
return expressjwt({
secret,
algorithms: ["HS256"],
isRevoked: isRevoked,
}).unless({
path: [
{ url: /\/api\/v1\/products(.*)/, methods: ["GET", "OPTIONS"] },
{ url: /\/api\/v1\/categories(.*)/, methods: ["GET", "OPTIONS"] },
`${api}/users/login`,
`${api}/users/register`,
],
});
}
async function isRevoked(req, payload, done) {
if (!payload.isAdmin) {
done(null, true);
}
done();
}
module.exports = authJwt;
The code for products.js which handles the GET, POST, PUT and DELETE requests for the products database is given below.
const { Product } = require("../models/product");
const express = require("express");
const { Category } = require("../models/category");
const router = express.Router();
const mongoose = require("mongoose");
router.get(`/`, async (req, res) => {
// localhost:3000/api/v1/products?categories=2342342,234234
let filter = {};
if (req.query.categories) {
filter = { category: req.query.categories.split(",") };
}
const productList = await Product.find(filter).populate("category");
if (!productList) {
res.status(500).json({ success: false });
}
res.send(productList);
});
router.get(`/:id`, async (req, res) => {
const product = await Product.findById(req.params.id).populate("category");
if (!product) {
res.status(500).json({ success: false });
}
res.send(product);
});
router.post(`/`, async (req, res) => {
const category = await Category.findById(req.body.category);
if (!category) return res.status(400).send("Invalid Category");
let product = new Product({
name: req.body.name,
description: req.body.description,
richDescription: req.body.richDescription,
image: req.body.image,
brand: req.body.brand,
price: req.body.price,
category: req.body.category,
countInStock: req.body.countInStock,
rating: req.body.rating,
numReviews: req.body.numReviews,
isFeatured: req.body.isFeatured,
});
product = await product.save();
if (!product) return res.status(500).send("The product cannot be created");
res.send(product);
});
router.put("/:id", async (req, res) => {
if (!mongoose.isValidObjectId(req.params.id)) {
return res.status(400).send("Invalid Product Id");
}
const category = await Category.findById(req.body.category);
if (!category) return res.status(400).send("Invalid Category");
const product = await Product.findByIdAndUpdate(
req.params.id,
{
name: req.body.name,
description: req.body.description,
richDescription: req.body.richDescription,
image: req.body.image,
brand: req.body.brand,
price: req.body.price,
category: req.body.category,
countInStock: req.body.countInStock,
rating: req.body.rating,
numReviews: req.body.numReviews,
isFeatured: req.body.isFeatured,
},
{ new: true }
);
if (!product) return res.status(500).send("the product cannot be updated!");
res.send(product);
});
router.delete("/:id", (req, res) => {
Product.findByIdAndRemove(req.params.id)
.then((product) => {
if (product) {
return res
.status(200)
.json({ success: true, message: "the product is deleted!" });
} else {
return res
.status(404)
.json({ success: false, message: "product not found!" });
}
})
.catch((err) => {
return res.status(500).json({ success: false, error: err });
});
});
router.get(`/get/count`, async (req, res) => {
const productCount = await Product.countDocuments((count) => count);
if (!productCount) {
res.status(500).json({ success: false });
}
res.send({
productCount: productCount,
});
});
router.get(`/get/featured/:count`, async (req, res) => {
const count = req.params.count ? req.params.count : 0;
const products = await Product.find({ isFeatured: true }).limit(+count);
if (!products) {
res.status(500).json({ success: false });
}
res.send(products);
});
module.exports = router;
Now, the codes for the users.js and categories.js are similar and thus I am not sharing it.
I am getting the problem when doing GET request for products using POSTMAN API. Even though I am passing the correct token using BEARER TOKEN field in the POSTMAN API, it is getting stuck at sending request. When I delete the isRevoked part, everything works fine, but then again I can't control the get request based on the isAdmin part. So, the problem is in the isRevoked part. But, what exactly is the issue. It seems fine to me logically.
the problem could arise from so many things, could not say without a deeper look at your code but, here are some suggestions:
should isRevoked be async?
does your payload contains isAdmin?
and if so, inside the if statement should be done(null, false) after the if statement you should get a userid or any sort of unique fields such as userEmail, ..., then use your userModel to query the user document so that your last done() be done(null, user)
This is a sample code of resolver and i want to test this with the jest on nestJS.
#Resolver()
export class UserResolver {
constructor(private readonly userService: UserService) {}
#UseGuards(GqlAccessGuard)
#Query(() => User)
async fetchUser(#CurrentUser() currentUser: ICurrentUser) {
return this.userService.findUserById({ id: currentUser.id });
}
#Mutation(() => User)
async createUser(#Args('createUserInput') createUserInput: CreateUserInput) {
return this.userService.create(createUserInput);
}
}
When I'm trying to test the "fetchUser" api of this resolver I'm stucked with the #UseGuard(). I don't know how can i import or provide the 'GQlAccessGuard' into the test code. Since I use the NestJs to build Graphql-codefirst server I used custom guard that extends AuthGuards to convert Context that request has.
export class GqlAccessGuard extends AuthGuard('access') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
#Injectable()
export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'jwt-access-token-key',
});
}
async validate(payload: any) {
return {
id: payload.sub,
email: payload.email,
role: payload.role,
};
}
}
const createUserInput: CreateUserInput = {
email: 'test#gmail.com',
name: 'test',
password: 'testpwd',
phone: '010-1234-5678',
role: Role.USER,
};
class MockGqlGuard extends AuthGuard('access') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
describe('UserResolver', () => {
let userResolver: UserResolver;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [JwtModule.register({})],
providers: [UserResolver, JwtAccessStrategy],
})
.useMocker((token) => {
if (token === UserService) {
return {
create: jest.fn().mockReturnValue({
id: 'testuuid',
email: 'test#gmail.com',
name: 'test',
password: 'testpwd',
phone: '010-1234-5678',
role: Role.USER,
}),
};
}
})
.overrideGuard(GqlAccessGuard)
.useValue(MockGqlGuard)
.compile();
userResolver = moduleRef.get<UserResolver>(UserResolver);
});
describe('create', () => {
it('should return user created', async () => {
const result: User = {
id: 'testuuid',
email: 'test#gmail.com',
name: 'test',
password: 'testpwd',
phone: '010-1234-5678',
role: Role.USER,
};
expect(await userResolver.createUser(createUserInput)).toStrictEqual(
result,
);
});
});
});
I'm so curious about this and spent several days to search about it. also want to know how can i deal with the customized decorator(createParamDecorator that i made) to use on the test code.
please help me on this and provide me with some references.
I am trying to use sinon to test a piece of code that is using an DynamoDB SDK method batchGet. Below the code:
const fetchSingleUser = async (userId) => {
try {
let queryParams = {RequestItems: {}};
queryParams.RequestItems['users'] = {
Keys: [{'UserId': userId}],
ProjectionExpression: 'UserId,Age,#UserName',
ExpressionAttributeNames: {'#UserName': 'Name'}
};
const res = await docClient.batchGet(queryParams).promise();
return res.Responses.users[0];
} catch (e) {
console.log('users::fetch::error - ', e);
}
};
Below the test using sinon:
'use strict';
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru();
let assert = require('assert');
describe('DynamoDB Mock Test', function () {
let AWS;
let scriptToTest;
let batchGetFunc;
before(function () {
batchGetFunc = sinon.stub();
AWS = {
DynamoDB: {
DocumentClient: sinon.stub().returns({
batchGet: batchGetFunc
})
}
};
scriptToTest = proxyquire('../index', {
'aws-sdk': AWS
});
});
it('Should scan using async/await and promise', async function () {
let result = { UserId: 'segf876seg876', Age: 33, Name: 'Paul' }
batchGetFunc.withArgs(sinon.match.any).returns({
promise: () => result
});
const data = await scriptToTest.fetchSingleUser('segf876seg876');
console.log('--data: ', data)
assert.equal(data.UserId, 'segf876seg876');
});
});
The Problem:
const data = await scriptToTest.fetchSingleUser('segf876seg876') always returns 'undefined'
Function fetchSingleUser always returns 'undefined' because you do not return anything after catch (after error happens). You only define return value on success.
But why errors happens, because const res does not contain Responses.users[0].
Simple solution:
change let result = { UserId: 'segf876seg876', Age: 33, Name: 'Paul' } to satisfy code Responses.users[0] to
const result = {
Responses: {
users: [{ UserId: 'segf876seg876', Age: 33, Name: 'Paul' }],
},
};
Note: use const if you not change variable value.
I am new to passport.js and trying to cover the unit test case for my JWT strategy. Can anyone suggest how to do that?
// Setup JWT strategy for all requests
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_PRIVATE_KEY,
},
async (jwtPayload: any, done: any) => {
const isUser = jwtPayload.type === EntityType.User;
const model = isUser ? userModel : vendorModel;
try {
const document = await model.findOne({ _id: jwtPayload.id });
if (document) {
return done(null, jwtPayload);
} else {
return done(null, false);
}
} catch (err) {
return done(err, false);
}
},
),
);
Unit test solution:
index.ts:
import passport from 'passport';
import { Strategy as JWTStrategy, ExtractJwt } from 'passport-jwt';
import { userModel, vendorModel, EntityType } from './models';
const JWT_PRIVATE_KEY = 'secret 123';
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_PRIVATE_KEY,
},
async (jwtPayload: any, done: any) => {
console.log('123123');
const isUser = jwtPayload.type === EntityType.User;
const model = isUser ? userModel : vendorModel;
try {
const document = await model.findOne({ _id: jwtPayload.id });
if (document) {
return done(null, jwtPayload);
} else {
return done(null, false);
}
} catch (err) {
return done(err, false);
}
},
),
);
models.ts:
export enum EntityType {
User = 'User',
}
export const userModel = {
async findOne(opts) {
return 'real user document';
},
};
export const vendorModel = {
async findOne(opts) {
return 'real vendor document';
},
};
index.test.ts:
import { Strategy as JWTStrategy, ExtractJwt, VerifyCallback, StrategyOptions } from 'passport-jwt';
import passport from 'passport';
import { userModel, vendorModel } from './models';
jest.mock('passport-jwt', () => {
const mJWTStrategy = jest.fn();
const mExtractJwt = {
fromAuthHeaderAsBearerToken: jest.fn(),
};
return { Strategy: mJWTStrategy, ExtractJwt: mExtractJwt };
});
jest.mock('passport', () => {
return { use: jest.fn() };
});
describe('62125872', () => {
let verifyRef;
beforeEach(() => {
const mJwtFromRequestFunction = jest.fn();
(ExtractJwt.fromAuthHeaderAsBearerToken as jest.MockedFunction<
typeof ExtractJwt.fromAuthHeaderAsBearerToken
>).mockReturnValueOnce(mJwtFromRequestFunction);
(JWTStrategy as jest.MockedClass<any>).mockImplementation((opt: StrategyOptions, verify: VerifyCallback) => {
verifyRef = verify;
});
});
it('should verify using user model and call done with jwtpayload if user document exists', async () => {
const payload = { type: 'User', id: 1 };
const mDone = jest.fn();
jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('mocked user document');
await import('./');
await verifyRef(payload, mDone);
expect(passport.use).toBeCalledWith(expect.any(Object));
expect(JWTStrategy).toBeCalledWith(
{ jwtFromRequest: expect.any(Function), secretOrKey: 'secret 123' },
expect.any(Function),
);
expect(ExtractJwt.fromAuthHeaderAsBearerToken).toBeCalledTimes(1);
expect(userModel.findOne).toBeCalledWith({ _id: 1 });
expect(mDone).toBeCalledWith(null, { type: 'User', id: 1 });
});
it("should verify using user model and call done with false if user document doesn't exist", async () => {
const payload = { type: 'User', id: 1 };
const mDone = jest.fn();
jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('');
await import('./');
await verifyRef(payload, mDone);
expect(passport.use).toBeCalledWith(expect.any(Object));
expect(JWTStrategy).toBeCalledWith(
{ jwtFromRequest: expect.any(Function), secretOrKey: 'secret 123' },
expect.any(Function),
);
expect(ExtractJwt.fromAuthHeaderAsBearerToken).toBeCalledTimes(1);
expect(userModel.findOne).toBeCalledWith({ _id: 1 });
expect(mDone).toBeCalledWith(null, false);
});
// you can do the rest parts
});
Unit test results:
PASS stackoverflow/62125872/index.test.ts
62125872
✓ should verify using user model and call done with jwtpayload if user document exists (11ms)
✓ should verify using user model and call done with false if user document doesn't exist (2ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 85 | 83.33 | 60 | 84.21 |
index.ts | 92.86 | 75 | 100 | 92.31 | 24
models.ts | 66.67 | 100 | 33.33 | 66.67 | 6,11
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.716s, estimated 10s
Using supertest to verify full cycle
import request from 'supertest';
import express from 'express';
import jwt from 'jsonwebtoken'
export const createAuthToken = (userId) => {
const body = {
type: EntityType.User,
id: userId,
};
return jwt.sign(body, JWT_PRIVATE_KEY);
};
// this function should configure express app
const appLoader = async app => {
(await import('../app/loaders/express')).expressLoader({ app }); // express bindings and routes
await import('./'); // passport config
}
describe('passport-jwt auth', () => {
const app = express();
const token = createAuthToken('user1')
beforeAll(async () => {
await appLoader({ app });
});
it('should verify auth', async () => {
jest.spyOn(userModel, 'findOne').mockResolvedValueOnce('mocked user document');
await request(app)
.get('/protected-endpoint')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
it('should verify auth - failure', async () => {
await request(app)
.get('/protected-endpoint')
.set('Authorization', `Bearer wrong-token`)
.expect(401);
});
});
how can i work with resolvers for mutations after i create type Mutations in graphql-yoga?
i've tried to create resolvers for mutations, but when i run in graph playground, i the code return error.
and here's my code:
const { GraphQLServer } = require('graphql-yoga')
// 1
const typeDefs = `
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!): User!
}
type User {
id: ID!
name: String!
}
`
// 2
const resolvers = {
Query: {
users: () => User,
},
Mutation: {
// WHAT SHOULD I WRITE IN HERE?
}
}
// 3
const server = new GraphQLServer({
typeDefs,
resolvers,
})
server.start(() => console.log(`Server is running on http://localhost:4000`))
if someone know how can i do for resolvers mutation, can shared with me?
thanks
Resolver for createUser can be defined as follows:
const resolvers = {
Query: {
// Query resolvers
},
Mutation: {
createUser: (parent, args) => {
// Business logic. Maybe save record in database
// Return created user. I am returning dummy data for now, so that you can test it in playground
return {id: 1, name: "John}
}
}
}
Finally it works for me.
i used this:
const resolvers = {
Query: {
users: () => User
},
Mutation: {
createUser: (source, {input}) => {
let newUser = [];
newUser.id = id;
newUser.name = input.name;
User.push(newUser);
return newUser;
}
}
}