Unit Testing BelongsToMany relations in Sequelize using Jasmine - javascript

I'm building a simple blog style application for practice using Node and Sequelize (with the Yeoman angular-fullstack generator). I'm trying to write out some tests for my article model, but I'm unable to figure out the right way to structure my tests. The articles and users are connected by a two-way belongsToMany relationship.
I'm able to verify that the setter and getter methods exist for my objects, but am unable to verify that they work. The code I'm trying to get to work is the code commented out at the end of the file. I'd appreciate any help in getting that unit test to work correctly, thanks!
'use strict';
/* globals describe, expect, it, beforeEach, afterEach */
var app = require('../..');
import request from 'supertest';
import {User, Article} from '../../sqldb';
var user = [];
var article = [];
var genArticles = function(i) {
for(var i = 0; i < 3; i++) {
article[i] = genArticle(i);
}
return article;
}
var genUsers = function(i) {
for(var i = 0; i < 3; i++) {
user[i] = genUser(i);
}
return user;
}
var genUser = function(i) {
return User.build({
provider: 'local',
name: `Fake User ${i}`,
email: `test${i}#example.com`,
password: 'password'
});
};
var genArticle = function(i) {
return Article.build({
title: `Fake Article ${i}`,
body: `test${i}`
});
};
describe("Article-User relations", function() {
before(function() {
return User.sync().then(function() {
return User.destroy({ where: {} }).then(function() {
return Article.sync().then(function() {
return Article.destroy({ where: {} }).then(function() {
});
});
});
});
});
beforeEach(function(done) {
genArticles();
genUsers();
done();
});
afterEach(function() {
return User.sync().then(function() {
return User.destroy({ where: {} }).then(function() {
return Article.sync().then(function() {
return Article.destroy({ where: {} }).then(function() {
});
});
});
});
});
it('should begin with no data', function() {
expect(User.findAll()).to
.eventually.have.length(0);
return expect(Article.findAll()).to
.eventually.have.length(0);
});
describe("Check methods exist and", function(done) {
before(function(done) {
article.forEach((a) => {a.save()});
user.forEach((a) => {a.save()});
done();
});
it("should have setter methods", function() {
expect(typeof article[0].setUsers).to.equal("function");
expect(typeof user[0].setArticles).to.equal("function");
});
it("should have getter methods", function() {
expect(typeof article[0].getUsers).to.equal("function");
expect(typeof user[0].getArticles).to.equal("function");
});
/* THIS IS THE TEST THAT ISN'T WORKING */
/*
it("setter and getter methods for article objects should work", function() {
return article[0].setUsers(user).then(() => {
return article[0].hasUsers(user).then( result => {
expect(result).should.eventually.equal.true;
})
}).catch( e => { console.log(e) });
//expect(typeof user[0].setArticles).to.equal("function");
});
*/
});
});

Related

Unable to retrieve object from constructor function when running Cypress

I'm trying to access my own JS module when running Cypress tests. I have defined my own module as...
cy.companies = function () {
const Company = function () {
this.id = 0;
this.name = "";
};
return {
Company: function () {
return Company;
}
};
}();
I have then modified \support\index.js to include this file...
import "../fixtures/provisioning/companies";
Then inside \support\commands.js I have added...
Cypress.Commands.add("createCompany", () => {
return new cy.companies.Company();
});
So that's the setup. I then consume it like this...
describe("...", () => {
beforeEach(function () {
const company = cy.createCompany();
console.log(company)
});
});
In the console I would expect to see...
{
id: 0,
name: ""
}
...but what I actually see is...
$Chainer {userInvocationStack: " at Context.eval (https://[...]/__cy…press\integration\[...].spec.js:54:22)", specWindow: Window, chainerId: "chainer2", firstCall: false, useInitialStack: false}
chainerId: "chainer2"
firstCall: false
...
Where have I gone wrong?
First issue is in your js module:
cy.companies = (function () {
const Company = function () {
this.id = 0;
this.name = name;
};
/* return {
Company: function () {
return Company;
}
}; */
return { Company };
})();
Second issue is due to the fact: cy commands are asynchronous and are queued to be run later, so you need to use .then
describe("...", () => {
beforeEach(function () {
// const company = cy.createCompany();
// console.log(company)
cy.createCompany().then((company) => {
cy.log(company);
console.log(company);
});
});
});

How to mock request to service with database?

I'm new in Jest and don't understand how to mock request to service for unit test.
EmployeeController.js
const EmployeeService = require('../services/employeeService');
exports.getEmployeeById = (req, res) => {
EmployeeService.find(req.params.employeeId) // need to be mocked
.then((employee) => {
if (employee == 0 || '') {
return res.status(404).json({
success: false,
message: 'Employee not found!'
});
} else {
return res.status(200).json({
success: true,
employee: employee
});
}
}).catch(err => {
res.status(404).json({
success: false,
message: 'Employee not found!'
});
});
}
EmployeeService.find - returns to me from the database the employee object by the entered Id in url.
EmployeeService.js
const sql = require('../config/connection');
const Promise = require('bluebird');
const connection = require('../config/connection');
const Employee = require('../models/employee.model');
var queryAsync = Promise.promisify(connection.query.bind(connection));
Employee.find = async function (employeeId) {
var result = queryAsync(
"SELECT empID, empName, IF(empActive, 'Yes', 'No') empActive, dpName FROM Employee INNER JOIN Department ON empDepartment = dpID WHERE empID = ? ", employeeId);
return result;
}
employee.model.js - model of employee.
const Employee = function (emp) {
this.empName = emp.empName;
this.empActive = emp.empActive;
this.empDepartment = emp.empDepartment;
this.creator = emp.creator;
};
module.exports = Employee;
Jest has built in utilities for stubbing out dependencies.
const employeeService = require("../services/employeeService");
const employeeController = require("./employeeController");
describe("employeeController", () => {
beforeEach(() => {
// this mock can be overridden wherever necessary, eg by using
// employeeService.find.mockRejectedValue(new Error("oh no"));
// by default, resolve with something that meets the needs of your consuming
// code and fits the contract of the stubbed function
jest.spyOn(employeeService, "find").mockResolvedValue(someMockQueryResult);
});
afterEach(() => {
jest.restoreAllMocks();
});
// contrived test to show how jest stubs can be used
it("fetches the employee", async () => {
await employeeController.getEmployeeById(123);
expect(employeeService.find).toHaveBeenCalledWith(123);
});
});
There are also options for mocking out the entire module. Check out the jest docs:
https://jestjs.io/docs/en/mock-functions.html
https://jestjs.io/docs/en/manual-mocks.html

Ember includedCommands: HTTPS request does not fetch

I am writing an ember includedCommand for fetching and updating the app/index.html file - which uses NodeJS https and fs module to replace the indexFile by calling a function BuildIndexFile, where I am facing a weird issue -
When I perform command ember server --update-index - I can see the BuildIndexFile is being called and the https request is made to the remote server which downloads the file and gets written by fs.writeFileSync in app/index.html.
But when I perform ember update-index which is an included command, I can see BuildIndexFile has been called, and it reaches till console.log('Fetching index.html'); and I believe it is calling https.request... but it closes from there, I have no idea why the call didn't go through, when I debugged using node --inspect-brk ./node_modules/.bin/ember update-index I can see the https is available on the file, but not executing.
I am attaching my sample code available as a in-repo-addon available at lib/hello/index.js -
/* eslint-env node */
'use strict';
const parseArgs = require('minimist');
const watchman = require('fb-watchman');
let client = new watchman.Client();
client.capabilityCheck({optional: [], required: ['relative_root']}, function (error, response) {
if (error) {
console.log(error);
}
console.log('Watchman', response);
});
const ServeCommand = require('ember-cli/lib/commands/serve');
const ARGS = parseArgs(process.argv.slice(2));
const fs = require('fs');
const https = require('https');
module.exports = {
name: 'hello',
isDevelopingAddon() {
return true;
},
includedCommands: function() {
var self = this;
return {
hello: ServeCommand.extend({
name: 'hello',
description: 'A test command that says hello',
availableOptions: ServeCommand.prototype.availableOptions.concat([{
name: 'updateindex',
type: String
}]),
run: function(commandOptions, rawArgs) {
console.log(commandOptions, rawArgs);
if (commandOptions['updateindex']) {
console.log('Update Index')
}
const sampleHelloPromise = sampleHello();
const servePromise = this._super.run.apply(this, arguments);
return Promise.all([sampleHelloPromise, servePromise]);
}
}),
updateIndex: {
name: 'update-index',
description: 'Update Index File',
availableOptions: [{
name: 'index-file',
type: String
}],
run: function(commandOptions, rawArgs) {
BuildIndexFile(self.project.root, 'https://yahoo.com', {});
}
}
}
},
preBuild: function(result) {
let self = this;
if (ARGS['update-index']) {
BuildIndexFile(self.project.root, 'https://google.com', {}).then(function() {
delete ARGS['update-index'];
})
.catch(function(e) {
console.log(e);
});;
}
}
};
async function sampleHello() {
return await new Promise(resolve => {
setTimeout(() => resolve('hello'), 2000);
})
}
const BuildIndexFile = (rootPath, target, headers) => {
try {
debugger;
const indexFile = `${rootPath}/app/index.html`;
let noIndexFile = !fs.existsSync(indexFile);
return new Promise(function (resolve, reject) {
let options = {
hostname: target.replace(/^http(?:s):\/\//i, ''),
port: 443,
method: 'GET'
};
let dataContent = '';
console.log('Fetching index.html');
var request = https.request(options, function(response) {
response.on('data', function(d) {
dataContent += d;
});
response.on('end', function() {
fs.writeFileSync(indexFile, dataContent);
return resolve();
});
});
request.on('error', function(e) {
console.log(e);
return reject(`Error: Creating Index File`);
});
request.end();
});
} catch(e) {
throw e;
}
}

Mocking service when testing controller in Karma for angularjs

I am working on the Single Page Application with AngularJS project for the Treehouse Full Stack JavaScript TechDegree and I am trying to do unit tests on the controllers. To test the controllers that make api calls to the dataService I have to mock the dataService and I can not figure out how to do this correctly. I have read article after article on unit testing angular and I am so lost that I have no idea what to do next.
controllers.js:
(function() {
'use strict';
angular.module('app')
.controller('RecipesController', function(dataService,$location) {
const vm = this;
vm.init = () => {
vm.hidden = true;
dataService.getAllRecipes(function(response) {
vm.recipes = response.data;
vm.getCategories(response.data);
});
}
vm.selectCategory = (category) => {
if (category === null) {
vm.init();
} else {
dataService.getCategory(category,function(response) {
vm.recipes = response.data;
});
}
};
vm.getCategories = (data) => {
let categories = new Set();
for (let item of data) {
categories.add(item.category);
}
vm.categories = Array.from(categories);
};
vm.addRecipe = () => {
$location.path('/add');
}
vm.deleteRecipe = (recipe,$index) => {
vm.toDelete = recipe.name;
vm.hidden = false;
vm.deleteIt = () => {
vm.hidden = true;
dataService.deleteRecipe(recipe._id,function(response) {
vm.init();
});
}
}
vm.init();
})
.controller('RecipeDetailController', function($scope,dataService,$location) {
const vm = this;
const init = () => {
const path = $location.path();
if (path.includes("edit")) {
let id = path.slice(6);
dataService.getID(id,function(response) {
vm.recipe = response.data;
vm.title = response.data.name;
vm.editCategory = response.data.category;
});
} else if (path.includes("add")) {
vm.recipe = {
name: "",
description: "",
category: "",
prepTime: 0,
cookTime: 0,
ingredients: [
{
foodItem: "",
condition: "",
amount: ""
}
],
steps: [
{
description: ""
}
]
}
vm.title = 'Add New Recipe.'
}
dataService.getAllCategories(function (response) {
vm.categories = response.data;
let index = response.data.findIndex(item => item.name === $scope.editCategory);
if (index === -1) {
vm.initial = {"name": "Choose a Category"};
} else {
vm.initial = $scope.categories[index];
}
});
dataService.getAllFoodItems(function (response) {
vm.foods = response.data;
});
}
vm.addItem = (item) => {
if (item === 'ingredient') {
vm.recipe.ingredients.push({amount: "amount", condition: "condition", foodItem: ""});
} else if (item === 'step') {
vm.recipe.steps.push({description: "description"});
}
};
vm.deleteItem = (item,$index) => {
if (item === 'ingredient') {
vm.recipe.ingredients.splice($index,1);
} else if (item === 'step') {
vm.recipe.steps.splice($index,1);
}
}
vm.saveChanges = (recipe) => {
vm.errors = [];
const buildErrorArray = (errorArray) => {
for (let item of errorArray) {
vm.errors.push(item.userMessage);
}
}
const collectErrors = (response) => {
if (response.data.errors.category) { buildErrorArray(response.data.errors.category) }
if (response.data.errors.ingredients) { buildErrorArray(response.data.errors.ingredients) }
if (response.data.errors.name) { buildErrorArray(response.data.errors.name) }
if (response.data.errors.steps) { buildErrorArray(response.data.errors.steps) }
}
if (recipe._id) {
dataService.updateID(recipe,function(response) {
$location.path('/');
}, function(response) {
collectErrors(response)
});
} else {
dataService.addRecipe(recipe,function(response) {
$location.path('/');
}, function(response) {
collectErrors(response)
});
}
}
vm.cancelChanges = () => {
$location.path('/');
}
init();
});
}());
services.js:
(function() {
'use strict';
angular.module('app')
.service('dataService', function($http,errors,httpErrors) {
this.getAllRecipes = function (callback) {
$http.get('http://localhost:5000/api/recipes')
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getAllCategories = function (callback) {
$http.get('http://localhost:5000/api/categories')
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getAllFoodItems = function (callback) {
$http.get('http://localhost:5000/api/fooditems')
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getCategory = function(category,callback) {
$http.get('http://localhost:5000/api/recipes?category=' + category)
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.getID = function (id,callback) {
$http.get('http://localhost:5000/api/recipes/' + id)
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
this.updateID = function (data,success,error) {
$http.put('http://localhost:5000/api/recipes/' + data._id, data)
.then(success,error).catch(errors.catch());
};
this.addRecipe = function (data,success,error) {
$http.post('http://localhost:5000/api/recipes', data)
.then(success,error).catch(errors.catch());
};
this.deleteRecipe = function (id,callback) {
$http.delete('http://localhost:5000/api/recipes/' + id)
.then(callback,httpErrors.display('HTTP Error'))
.catch(errors.catch());
};
});
}());
controllersSpec.js:
describe("Unit Testing Controllers", function() {
beforeEach(angular.mock.module('app'));
let $scope;
let getAllRecipesMock;
beforeEach(inject(function(_$controller_,_$rootScope_,$q) {
$controller = _$controller_;
$scope = _$rootScope_.$new();
getAllRecipesMock = {
getAllRecipes: function() {
var deferred = $q.defer();
deferred.resolve([{name: "recipename"}]);
return deferred.promise;
}
}
}));
it('has a test to test that tests are testing', function() {
expect(2 + 2).toEqual(4);
});
it('should have a RecipesController', function() {
const controller = $controller('RecipesController',{$scope:$scope});
expect(controller).toBeDefined();
});
it('should have a RecipeDetailController', function() {
const controller = $controller('RecipeDetailController',{$scope:$scope});
expect(controller).toBeDefined();
});
it('should call the getAllRecipes service and return response', inject(function() {
const controller = $controller('RecipesController',{$scope:$scope,dataService:getAllRecipesMock});
$scope.$digest();
expect(controller.recipes).toBe([{name: "recipename"}]);
}));
it('should remove duplicate categories', function() {
const controller = $controller('RecipesController',{$scope:$scope});
let data = [{'category':'dog'},{'category':'cat'},{'category':'horse'},{'category':'dog'},{'category':'cow'}];
controller.getCategories(data);
expect(controller.categories).toEqual(['dog','cat','horse','cow']);
});
it('should take you to the /add route when the addRecipe method is called', inject(function($location) {
const controller = $controller('RecipesController',{$scope:$scope});
controller.addRecipe();
expect($location.path()).toEqual('/add');
}));
});
This is the result I get when I run the tests:
Unit Testing Controllers
√has a test to test that tests are testing
√should have a RecipesController
√should have a RecipeDetailController
×should call the getAllRecipes service and return response
Expected undefined to be [ Object({ name: 'recipename' }) ].
at Object.<anonymous> (test/controllersSpec.js:38:32)
at Object.invoke (node_modules/angular/angular.js:4839:19)
at Object.WorkFn (node_modules/angular-mocks/angular-mocks.js:3155:20)
√should remove duplicate categories
√should take you to the /add route when the addRecipe method is called
Chrome 55.0.2883 (Windows 10 0.0.0): Executed 6 of 6 (1 FAILED) (0.235 secs / 0.084 secs)
TOTAL: 1 FAILED, 5 SUCCESS
1) should call the getAllRecipes service and return response
Unit Testing Controllers
Expected undefined to be [ Object({ name: 'recipename' }) ].
at Object.<anonymous> (test/controllersSpec.js:38:32)
at Object.invoke (node_modules/angular/angular.js:4839:19)
at Object.WorkFn (node_modules/angular-mocks/angular-mocks.js:3155:20)
EDIT
I decided to change the service to return a promise instead of a callback:
this.getAllRecipes = function () {
return $http.get('http://localhost:5000/api/recipes');
};
Then I changed the corresponding function in the controller:
vm.init = () => {
vm.hidden = true;
let allRecipes = dataService.getAllRecipes();
allRecipes.then(function(response) {
vm.recipes = response.data;
vm.getCategories(response.data);
},httpErrors.display('HTTP Error'))
.catch(errors.catch());
}
but I'm still getting
Expected undefined to be [ Object({ name: 'recipename' }) ].
Am I not implementing the promise correctly? Is there still something in my test that I'm missing?
You are currently mixing callbacks and promises.
The method getAllRecipes in the real service implementation takes a callback as an argument and executes it when the internal ajax call is done. The consumer of the method has no idea that the implementation uses promises internally.
The mock implementation of getAllRecipes however does not take or use a callback function but instead returns a promise.
You have in your controller:
dataService.getAllRecipes(function(response) {
vm.recipes = response.data;
vm.getCategories(response.data);
});
But with the mock implementation you would need:
dataService.getAllRecipes(function(response) {
vm.recipes = response.data;
vm.getCategories(response.data);
}).then(function (response) {
// Code
});
With your current implementation of getAllRecipes your mock could look like this:
getAllRecipesMock = {
getAllRecipes: function(callback) {
var response = {
data: [{
name: "recipename"
}]
};
callback(response);
}
};
Also note that unless you want to compare for reference equality, use toEqual instead of toBe:
expect(controller.recipes).toEqual([{
name: "recipename"
}]);
Demo: http://plnkr.co/edit/5BQBt4tTxohXEN0Drq3f?p=preview
An alternative is to change the service implementation to return a promise instead of using callbacks.

mocking MongoDB with proxyquire

I'd like to mock MongoDB dependency with proxyquire
by doing this in my test:
var proxyquire = require('proxyquire');
var controller = path.resolve('.path/to/controller/file.js');
in the before each statement:
mocked_mongoose = {
isMocked: true,
model: function(name, schema, collection, skipInit) {
return {
find: function(conditions, projection, options, callback) {
console.log('callback find');
return callback();
},
save: function(options, fn) {
console.log('callback save');
return callback();
},
findOne: function(conditions, projection, options, callback) {
console.log('callback find one');
var model = mongoose.model(name);
var fakeModel = fakery.fake(model);
return callback(null, fakemodel);
}
}
}
};
proxyquire(controller, {
'mongoose': mocked_mongoose
});
and when I go to the controller and do
console.log(mongoose.isMocked) I got undefined and if I print mongoose.model.toString() seems like the mongoose methods aren't overridden.
I followed up this article and tried to implement the same logic, but I'm not getting the same results.
any help will be appreciated,
thanks!
I ended up by changing the way I was defining my mocked mongoose object, to match exactly the scenarios I wanted to test in mt case:
first model instantiation
var Model = mongoose.model('SchemaDef');
var other = Model({
_id:'someId'
name'Some other fields'
});
model search:
Model.findOne(query, callback);
this the version how it works:
'use strict';
var factory = require('factory-girl');
var mongoose = require('mongoose');
function viewModel(name){
function constructor(obj){
if(obj){
obj.find = constructor.find;
obj.save = constructor.save;
obj.findOne = constructor.findOne;
}
return obj;
};
constructor.isMocked = true;
constructor.find = function(query, callback){
factory.build(name, function(err, mockedModel){
return callback(null,[mockedModel]);
});
};
constructor.save = function(callback){
factory.build(name, function(err, mockedModel){
return callback(null,[mockedModel]);
});
};
constructor.findOne=function(query,callback){
factory.build(name, function(err, mockedModel){
return callback(null,mockedModel);
});
};
return constructor;
};
module.exports = {
model:function(name){
factory.define(name, mongoose.model(name));
return viewModel(name);
}
};

Categories

Resources