I need to mock the data received from S3, but even using spyOn, the upload feature is called and I get errors. How can I intercept and return mocked data?
my service
async uploadAvatar(userId: string, avatar: Express.Multer.File) {
const user = await this.repository.findOne(userId);
const s3Instance = new S3Storage();
if (user.avatar_key) {
const formatImagePath = user.avatar_key.replace(/\.[^.]*$/, '');
await s3Instance.delete(formatImagePath);
}
const S3Response = await s3Instance.upload(avatar);
console.log(S3Response);
const saveAvatar = await this.repository.updateAvatar(
userId,
S3Response.Key,
);
const avatarUrl = `${process.env.AWS_CLOUD_URL}/${saveAvatar.avatar_key}`;
return { ...saveAvatar, avatar_key: avatarUrl };
}
my test
describe('uploadAvatar', () => {
it('should be able upload user avatar', async () => {
const userId = '1';
const imageMock: Express.Multer.File = {
mimetype: 'image/png',
filename: '',
fieldname: '',
originalname: '',
encoding: '',
size: 1000000,
stream: undefined,
destination: '',
path: '',
buffer: Buffer.from(''),
};
const userAvatarUpload = {
id: '',
name: '',
email: '',
phone: null,
avatar_key: `${process.env.AWS_CLOUD_URL}/image`,
gender: null,
birthday: null,
language: null,
password: '',
last_login: new Date(),
created_at: new Date(),
updated_at: new Date(),
};
const responseS3: ManagedUpload.SendData = {
Location: '',
ETag: '',
Bucket: '',
Key: '',
};
jest.spyOn(repository, 'findOne').mockResolvedValue(userEntity);
const s3Instance = new S3Storage();
jest.spyOn(s3Instance, 'upload').mockResolvedValue(responseS3);
jest
.spyOn(repository, 'updateAvatar')
.mockResolvedValue(userAvatarUpload);
const result = await userService.uploadAvatar(userId, imageMock);
expect(s3Instance).toBeDefined();
expect(result).toHaveProperty('avatar_key');
});
Related
I am trying to write test cases for an Node.js backend project
The database is using pg-promise. I run into issue when trying to stub the repository and it tries to call other repositories.
Here is the repository file user.js
async findByRefToken(refToken) {
return await this.db.oneOrNone(`SELECT id FROM useraccount WHERE username = $1`, refToken);
}
async createNormal(data) {
// generate a verify email token
const verifyToken = await this.generateToken();
// create unique ref code
const tokenid = nanoid(Number(env.REF_TOKEN_LENGTH));
const refToken = await this.generateUniqueRefToken(tokenid);
// find the referral's id from the ref token
let referredBy = null;
if(data.referred) {
referredBy = await this.findByRefToken(data.referred);
}
const meta = {
dob: data.dob,
gender: data.gender,
country: data.country,
phone: data.phone,
email_verified: false,
verify_token: verifyToken,
ref_token: refToken,
referred: referredBy ? referredBy.id : null
};
const verificationMeta = {
kyc_verified: false
};
const normalUser = {
id: data.userId,
first_name: data.firstName.trim(),
last_name: data.lastName.trim(),
active: true,
email: data.email.trim().toLowerCase(),
username: data.username.trim(),
role: 'Normal',
password: bcrypt.hashSync(data.password, Number(env.SALT_ROUNDS)),
meta: meta,
verification_meta: verificationMeta
};
const newUser = await this.db.one(CREATE_NORMAL_USER, normalUser);
return {
id: newUser.id,
firstName: data.firstName,
email: data.email,
verifyToken: verifyToken,
refToken: refToken,
referredBy: referredBy
};
}
I am trying to test the createNormal function, but it would also call other repo function like findByRefToken and await this.db.one(CREATE_NORMAL_USER, normalUser); Is there anyway to stub them away?
And here is the test written user.test.js
const chai = require("chai");
const sinon = require("sinon");
const expect = chai.expect;
const {faker} = require("#faker-js/faker");
const UserRepository = require("../../repos/user");
describe("UserRepository", function() {
const stubValue = {
dob: faker.date.birthdate(),
gender: faker.name.gender(),
country: faker.address.country(),
phone: faker.phone.number(),
email_verified: false,
id: faker.datatype.uuid(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
username: faker.name.fullName(),
password: faker.random.alphaNumeric(5),
email: faker.internet.email(),
verifyToken: faker.random.alphaNumeric(12),
refToken: faker.random.alphaNumeric(12),
referredBy: faker.random.alphaNumeric(12)
};
describe("create", function () {
it("should add a new user to the db", async function () {
// const stub = sinon.stub(UserRepository, "createNormal").resolves(stubValue.refToken);
const userRepository = new UserRepository();
const user = await userRepository.createNormal(stubValue);
expect(user.id).to.equal(stubValue.id);
expect(user.name).to.equal(stubValue.name);
expect(user.phone).to.equal(stubValue.phone);
expect(user.id).to.equal(stubValue.id);
expect(user.verifyToken).to.equal(stubValue.verifyToken);
});
});
});
Thank you for responding
The method creates and saves a new user in the database. What is left to test if you stub the database calls?
My recommendation would be to not mock; set up the appropriate structures in the database and use them. You can use factory-bot to make this simpler.
Note: it's strange that it doesn't return a user.
I'd extract making the user from saving the user. I'd also put all the token and referral stuff into their own methods.
async newNormal(data) {
const verifyToken = await this.generateToken();
// create unique ref code
const tokenid = this.generateTokenId();
const refToken = await this.generateUniqueRefToken(tokenid);
const referredBy = await this.findByRefToken(data.referred);
const meta = {
dob: data.dob,
gender: data.gender,
country: data.country,
phone: data.phone,
email_verified: false,
verify_token: verifyToken,
ref_token: refToken,
referred: referredBy ? referredBy.id : null
};
return {
id: data.userId,
first_name: data.firstName.trim(),
last_name: data.lastName.trim(),
active: true,
email: data.email.trim().toLowerCase(),
username: data.username.trim(),
role: 'Normal',
password: bcrypt.hashSync(data.password, Number(env.SALT_ROUNDS)),
meta: meta,
verification_meta: {
kyc_verified: false
};
};
}
async createNormal(data) {
const userData = await this.newNormal(data);
return await this.db.one(CREATE_NORMAL_USER, userData);
}
Now createNormal is an integration method. All it does is call newNormal and pass the result through to a SQL query. You can test it by mocking newNormal and db.one.
The important work is happening in newNormal, focus testing on that. You can mock generateToken, generateTokenId, generateUniqueRefToken and findByRefToken. But, again, this test would be easier and more realistic by just setting up the necessary data.
I have the mirage models:
// mirage/models/country.js
import { Model, belongsTo, hasMany } from 'miragejs';
export default Model.extend({
name: '',
iso3166_1_alpha3: '',
capitol_city: belongsTo('city', {inverse: null}),
cities: hasMany('city', {inverse: 'country'})
});
and:
// mirage/models/city.js
import { Model, belongsTo } from 'miragejs';
export default Model.extend({
name: '',
country: belongsTo('country', {inverse: 'cities'})
});
and the serializer:
// mirage/serializers/application.js
import { camelize, capitalize, underscore } from '#ember/string';
import { JSONAPISerializer } from 'miragejs';
export default class ApplicationSerializer extends JSONAPISerializer
{
alwaysIncludeLinkageData = true;
keyForAttribute(attr) {
return underscore(attr);
};
keyForRelationship(modelName) {
return underscore(modelName);
};
typeKeyForModel(model) {
return capitalize(camelize(model.modelName));
};
};
When I run the tests:
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Mirage | mirage models', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
test('it retrieves the country', async function (assert) {
const server = this.server;
let city = server.create('city', { id: '1', name: 'Paris' });
server.create(
'country',
{
id: 'FR',
name: 'France',
iso3166_1_alpha3: 'FRA',
capitol_city: city
}
);
let response = await fetch('/api/countries')
assert.strictEqual(response.status, 200, "Should have created the model");
let json = await response.json();
assert.deepEqual(
json,
{
data: [
{
type: 'Country',
id: 'FR',
attributes: {
name: 'France',
iso3166_1_alpha3: 'FRA',
},
relationships: {
capitol_city: {data: {type: 'City', id: '1'}},
cities: {data: []},
}
}
]
}
)
});
test('it creates the country', async function (assert) {
const server = this.server;
server.create('city', { id: '1', name: 'Paris' });
let response = await fetch(
'/api/countries',
{
method: 'POST',
headers: {'Countent-Type': 'application/json'},
body: JSON.stringify(
{
data: {
id: 'FR',
type: 'Country',
attributes: {
iso3166_1_alpha3: 'FRA',
name: 'France',
},
relationships: {
capitol_city: { data: { type: 'City', id: '1'} },
cities: { data: [{ type: 'City', id: '1'}] }
}
}
}
)
}
);
console.log((await response.json()).message);
assert.strictEqual(response.status, 201, "Should have created the model");
});
});
The first one passes and the second one fails with the message:
Mirage: You're passing the relationship 'capitol_city' to the 'country' model via a POST to '/api/countries', but you did not define the 'capitol_city' association on the 'country' model.
How can I get Mirage to recognise the capitol_city attribute on the model?
Mirage is opinionated with regards to the format of attributes and expects the attributes to be in camelCase (and not snake_case).
Unfortunately the Ember CLI Mirage model relationships documentation does not mention this expectation and all the examples use single-word attributes. Even more unfortunately, Mirage will work with snake_case attributes for simple GET requests and when directly creating models through the API; it is only when you make a request to POST/PUT/PATCH a model into the server that it fails and the message will (confusingly) refer to the snake case attribute which has been defined. (See the Mirage source code for where it fails.)
To solve it, convert the attributes to camel case:
// mirage/models/country.js
import { Model, belongsTo, hasMany } from 'miragejs';
export default Model.extend({
name: '',
iso31661Alpha3: 0,
capitolCity: belongsTo('city', {inverse: null}),
cities: hasMany('city', {inverse: 'country'})
});
and change it in the tests as well:
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Mirage | mirage models', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
test('it retrieves the country', async function (assert) {
const server = (this as any).server;
let city = server.create('city', { id: '1', name: 'Paris' });
server.create(
'country',
{
id: 'FR',
name: 'France',
iso31661Alpha3: 'FRA',
capitolCity: city
}
);
let response = await fetch('/api/countries')
assert.strictEqual(response.status, 200, "Should have created the model");
let json = await response.json();
console.log(JSON.stringify(json));
assert.deepEqual(
json,
{
data: [
{
type: 'Country',
id: 'FR',
attributes: {
name: 'France',
iso3166_1_alpha3: 'FRA',
},
relationships: {
capitol_city: {data: {type: 'City', id: '1'}},
cities: {data: []},
}
}
]
}
)
});
test('it creates the country', async function (assert) {
const server = (this as any).server;
let city = server.create('city', { id: '1', name: 'Paris' });
let response = await fetch(
'/api/countries',
{
method: 'POST',
headers: {'Countent-Type': 'application/json'},
body: JSON.stringify(
{
data: {
id: 'FR',
type: 'Country',
attributes: {
iso3166_1_alpha3: 'FRA',
name: 'France',
},
relationships: {
capitol_city: { data: { type: 'City', id: '1'} },
cities: { data: [{ type: 'City', id: '1'}] }
}
}
}
)
}
);
console.log((await response.json()).message);
assert.strictEqual(response.status, 201, "Should have created the model");
});
});
However, once you convert it to camel case then the attribute iso31661Alpha3 does not get formatted correctly in the output so you have to manually change the serializer for the country model:
// mirage/serializers/country.js
import ApplicationSerializer from './application';
export default class CountrySerializer extends ApplicationSerializer
{
keyForAttribute(attr: string) {
switch(attr)
{
case 'iso31661Alpha3': return 'iso3166_1_alpha3';
default: return super.keyForAttribute(attr);
}
};
};
Once the attributes are in the correct case then it will work.
i want to create dynamic model, repository and controller
export async function dynamicModelsDemo(app: any, modelData: any): Promise<boolean> {
console.log("ModelData",modelData);
// assume that this def can be created dynamically (at runtime), e.g. from database info
const modelDef = new ModelDefinition({
name: 'contact',
properties: {
id: {
type: 'Number',
required: true,
length: null,
precision: 10,
scale: 0,
id: 1,
},
name: {
type: 'String',
required: false,
length: 512,
precision: null,
scale: null,
},
},
});
// tryin' to extend Entity with new fields
const DynamicModel = defineModelClass<typeof Entity, {id: number; title?: string}>(
Entity,
modelDef,
);
const BookRepository = defineCrudRepositoryClass(DynamicModel);
inject(`datasources.memory`)(BookRepository, undefined, 0);
const repoBinding = app.repository(BookRepository);
const basePath = '/contact';
const DynamicController0 = defineCrudRestController(DynamicModel, {basePath});
inject(repoBinding.key)(DynamicController0, undefined, 0);
app.controller(DynamicController0);
console.log(basePath);
return new Promise(function (resolve, reject) {
resolve(true);
});
}
i need help that how should i create Post method which would receive request body and that body would pass to my function above i mentioned,
Currently i'm calling dynamicModelsDemo function by this endpoint,
#get('/ping/build', {
modelData : {},
responses: {
'200': {
description: 'Test models assemble',
},
},
})
async build(): Promise<boolean> {
return dynamicModelsDemo(this.localApp,this.modelData);
}
i want to convert this #get to #post so i can pass my requested body to this function..
This is working so fine, I thing this is what I was looking for:
#post('ping/createobject')
async createObject(
#requestBody() model: any
):Promise<boolean> {
return dynamicModelsDemo(this.localApp,model);
}
In my controller i have a function that creates a user but also checks to make sure that the user does not already exist and then a dashboard function which gets the user from the request and returns any petitions that have been created by that user.
I've looked at mocha, chai and sinon to carry out the tests along with various online resources but have no idea how to begin testing these two functions since they rely on models. Can anyone point me in the right direction to testing the controller or know of any resources which maybe able to help me?
Controller:
const bcrypt = require('bcryptjs');
const passport = require('passport');
const Users = require('../models/Users');
const Petitions = require('../models/Petitions');
const UserController = {
async register(req, res) {
const {name, email, password, passwordCon} = req.body;
let errors = []
// check required fields
if (!name || !email || !password || !passwordCon) {
errors.push({ msg: 'Please enter all fields' });
}
// check passwords match
if (password !== passwordCon) {
errors.push({ msg: 'Passwords do not match' });
}
// check password length
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
// if validation fails, render messages
if (errors.length > 0) {
res.render('user/register', {
errors,
name,
email,
password,
passwordCon
})
} else {
// validation passed
Users.findOne({email: email})
.then(user => {
if (user) {
// user exists
errors.push({msg: 'Email already in use'});
res.render('user/register', {
errors,
name,
email,
password,
passwordCon
});
} else {
const newUser = new Users({
name: name,
email: email,
password: password
});
// hash password
bcrypt.genSalt(10, (error, salt) =>
bcrypt.hash(newUser.password, salt, (error, hash) => {
if (error) throw error;
// set password to hashed
newUser.password = hash;
// save user
newUser.save()
.then(user => {
req.flash('success_msg', 'Registration Success');
res.redirect('/user/login');
})
.catch(error => console.log(error));
}))
}
});
}
},
async dashboard(req, res) {
const user = req.user;
const petitions = await Petitions.find({createdBy: user._id});
console.log('here');
res.render('user/dashboard', {
user: req.user,
petitions: petitions
})
}
};
module.exports = UserController;
Models:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
createdOn: {
type: Date,
default: Date.now
},
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
petitions: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Petitions' }
]
})
const Users = mongoose.model('Users', UserSchema);
module.exports = Users;
const mongoose = require('mongoose');
const PetitionSchema = new mongoose.Schema({
createdOn: {
type: Date,
default: Date.now
},
title: {
type: String,
required: true
},
signaturesNeeded: {
type: String,
required: true
},
description: {
type: String,
required: true
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users'
},
signatures: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Users' }
]
})
const Petitions = mongoose.model('Petitions', PetitionSchema);
module.exports = Petitions;
I no any idea how test updateTotal... if requestAxios is success return callback function updateTotal but how i spy that?
...methods:{
updateAll() {
const updateTotal = (request) => {
this.total = request.data.total
}
this.requestAxios(
'get',
'/api/',
{},
[updateTotal],
)
}
}...
requestAxios:
async requestAxios(
method = 'get',
url = '',
objSend = {},
successFunctions = [],
errorsFunctions = [],
formKey = 'form',
) {
let request = ''
if (method !== 'delete') {
request = await axios[method](url, objSend, this.headerRequestJson)
.then(response => this.responseRequestText(response))
.catch(errors => this.responseRequestText(errors.response));
} else {
request = await axios.delete(url, {
data: objSend,
headers: this.headerRequestJson.headers,
})
.then(response => this.responseRequestText(response))
.catch(errors => this.responseRequestText(errors.response));
}
if (request.status === 'success') {
// success callback fn
successFunctions.forEach((value) => {
value(request, formKey)
})
} else {
// errors callback fn
errorsFunctions.forEach((value) => {
value(request)
})
// adicionar erros nos campos
this.addErrors(request, formKey);
}
},
My attempt:
test('updateTotalFinancial: ', () => {
const update = jest.fn()
const response = {
data: {
total: 100,
},
}
const requestAxios = jest.fn(() => update(response))
const wrapper = shallowMount(ModalUnderwriting, {
store,
localVue,
methods: {
requestAxios,
},
})
wrapper.setData({
total: '0',
})
wrapper.vm.updateTotalFinancial()
first expect success second not, not update data/variabel total
expect(update).toBeCalled()
expect(wrapper.vm.total).toEqual(100)