Unable to retrieve object from constructor function when running Cypress - javascript

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);
});
});
});

Related

In Jest, how do I cause a function called within the function to return a specific value

This is the function I am testing (stripped down for simplicity's sake):
populate.js->
const { createSessionID } = require('./populate-template-utilities');
const createFile = async () => {
const responseHeader = {};
responseHeader.SessionID = createSessionID();
return responseHeader;
};
module.exports = {
createFile,
};
The function this function calls:
populate-template-utilities ->
const createSessionID = () => {
const digits = (Math.floor(Math.random() * 9000000000) + 1000000000).toString();
return `PAX${digits}`;
};
module.exports = {
createSessionID,
};
And my unit test (again stripped down):
const { createSessionID } = require('../app/lib/populate-template-utilities');
describe('create XML for output files', () => {
const mockID = jest
.spyOn(createSessionID)
.mockImplementation(() => 'PAX123456');
it('should create a PAX File', async () => {
const result = await createFile();
expect(result).toEqual(getFile);
});
});
I want createSessionID to return 'PAX123456' and think mockID should do it, but it's erroring with:
Cannot spy the undefined property because it is not a function; undefined given instead
The spyOn method needs at least two parameters: object and methodName.
Try sth like this:
import * as populateTemplateUtils from "../sessionStuff";
import { createFile } from "../createFile";
describe('create XML for output files', () => {
it('should create a PAX File', async () => {
jest
.spyOn(populateTemplateUtils, 'createSessionID')
.mockReturnValue('PAX123456');
const result = await createFile();
expect(result).toEqual({"SessionID": "PAX123456"});
});
});
It all started to work when I changed the:
module.exports = {
createSessionID,
};
to:
export const createSessionID = () => {

Jest mock test function inside function Error: Cannot read property 'ids' of undefined

This is the selector
var getId = function getId(state) {
return state.ids.id;
};
This is the function which I'm trying to write jest for
export function triggerUpdate() {
store.dispatch(retrieveData(getId(store.getState())));
}
here is the test which i've written. But test is failing:
Error: Cannot read property 'ids' of undefined.
describe('triggerUpdate', () => {
it('should call the update', () => {
const expectedPayload = [
{ type: 'RETRIEVE_ID', chorusActions: [] },
];
console.log(store.getState()); // this is coming as empty
const spy = jest.spyOn(store, 'dispatch');
expect(spy).toHaveBeenCalledWith(expectedPayload);
});
});
I need to mock getId() but tried different ways.
const idSelector = require('path');
describe('triggerUpdate', () => {
it('should call the update', () => {
idSelector.getId = jest.fn().mockReturnValue('mockId'); // this is how selector is mocked.
const expectedPayload = [
{ type: 'RETRIEVE_ID', chorusActions: [] },
];
const spy = jest.spyOn(store, 'dispatch');
expect(spy).toHaveBeenCalledWith(expectedPayload[0]);
});
});

Mock call in Typescript in unit test using only Mocha

I have the following method:
import { ObjectDal } from "./ObjectDal";
export class ObjectBL {
async getObject(id) {
try {
let dal = new ObjectDal();
let result = await dal.get(id);
return result;
} catch (err) {
// log the error
}
}
where the ObjectDal class is:
export class ObjectDal {
async get(id) {
// open connection to db
// make a query based on id
// put the result in a `result` variable
return result;
}
}
I have to write an unit test for the getObject() method using only Mocha...
This is the begining of the UT:
const assert = require('assert');
const ObjectBL = require("../ObjectBL");
describe('Something', () => {
describe('...', () => {
it('getObject_GetsObjectUsingID_True', async () => {
// arange
let id = "123456789101";
let expected = {
"name": "ana",
"hasApples": true
};
let test = new ObjectBL.ObjectBL();
let result = await test.getObject(id);
assert.deepStrictEqual(result, expected);
});
});
});
But in this case I would have to call the method from the ObjectDal class...
How can I mock the call to the get() method using only Mocha?
I found answers with Sinon, or Mocha with Sinon and/or Chai... but nothing with only Mocha...
Proxies might be the way to go for you.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
You could mok methods by using a Proxy like so:
const assert = require('assert');
const ObjectBL = require("../ObjectBL");
describe('Something', () => {
describe('...', () => {
it('getObject_GetsObjectUsingID_True', async () => {
// arange
let id = "123456789101";
let expected = {
"name": "ana",
"hasApples": true
};
let test = new ObjectBL.ObjectBL();
const handler = {
get: function(obj, prop) {
// mok the getObject method
if(prop === 'getObject'){
return () => {
return Promise.resolve({
"name": "ana",
"hasApples": true
});
}
} else {
return obj[prop];
}
}
};
const p = new Proxy(test, handler);
let result = await p.getObject(id);
assert.deepStrictEqual(result, expected);
});
});
});
If you ONLY want to mok the ObjectDal.get method, you might want to override the prototype and recover it afterwards:
const assert = require('assert');
const ObjectBL = require("../ObjectBL");
const ObjectDal = require("../ObjectDal");
describe('Something', () => {
describe('...', () => {
it('getObject_GetsObjectUsingID_True', async () => {
// arange
let id = "123456789101";
let expected = {
"name": "ana",
"hasApples": true,
};
const proto = Object.getOwnPropertyDescriptor(ObjectDal, 'prototype').value;
const backup = proto.get;
proto.get = () => {
return Promise.resolve({
"name": "ana",
"hasApples": true,
});
}
let test = new ObjectBL.ObjectBL();
let result = await test.getObject(id);
ObjectDal.prototype.get = backup;
assert.deepStrictEqual(result, expected);
});
});
});
You could also override the ObjectDal with a Proxy and implement the construct handler to return a dummy ObjectDal, but this might be more tricky, since you are working with modules.
Testing is feedback, not just on whether or not your code works as advertised but even more crucially on the quality of your design.
The fact that you are having trouble writing the tests is your first sign you did something sub-optimal in the implementation. What you want is this:
export class ObjectBL {
constructor (dal) {
this.dal = dal;
}
async getObject(id) {
try {
let result = await this.dal.get(id);
return result;
} catch (err) {
// log the error
}
}
...and now the dependency is clear rather than implicit and will show up in editor tooltips, is more amenable to static analysis, etc. And it solves your problem: now you can mock it easily for testing, no further libraries needed.

Unit Testing BelongsToMany relations in Sequelize using Jasmine

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");
});
*/
});
});

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.

Categories

Resources