How to creating mock objects with Jasmine using createSpy() - javascript

I’m trying to set up a mock unit test with jasmine by using createSpy(). I’ve getting
TypeError: undefined is not a function (evaluating jasmine.createSpy().andCallFake(function(msg) { return [] })) (line 13).
CODE:
$scope.workInit = function() {
$scope.work_loading = true;
$scope.public_work_loading = true;
var projects = SomeService.getGroups();
var publicProjects = SomeService.getPublicGroupings('G');
…
...
}
TEST:
this.SomeService = {
getGroups: jasmine.createSpy().andCallFake(function(msg) { return [] }),
getPublicGroupings: jasmine.createSpy().andCallFake(function(msg) { return [] }),
}
it('should expect work_loading and public_loading to be false', function () {
this.scope.workInit();
expect($scope.work_loading).toEqual(false);
expect($scope.public_work_loading).toEqual(false);
});

The way you have set up the mock unit test case is correct, the same works for me in this fiddle.
Check the scope in which you've included this part of the code:
this.SomeService = {
getGroups: jasmine.createSpy().andCallFake(function(msg) { return [] }),
getPublicGroupings: jasmine.createSpy().andCallFake(function(msg) { return [] }),

Related

Mock DOM method jest

I am have trouble trying to make a unit test for this function. The problem is it using a lib noUiSlider for a range slider and when the test get there , it does not recongnise noUiSlider.set. How do I correctly mock this
TypeError: Cannot read property 'set' of undefined
function popState(){
if (rangeSlider) {
$('.range-reset.' + name).removeClass('hidden');
var element = self.isRangeElement(name).element;
var unit = element.getAttribute('data-unit');
//since noUiSlider accepts no unit,Remove unit from values
unit = new RegExp(unit, 'g');
value = value.replace(unit, '');
value = value.split('-');
***element.noUiSlider.set(value);***
}
}
I have tried this approach it did not work
import { JSDOM } from 'jsdom';
const dom = new JSDOM();
dom.noUiSlider = {
set: jest.fn()
} ;
global.window = dom.window;
And I have tried this as well none work
Object.defineProperty(window.document, 'noUiSlider', {
set: jest.fn(),
});
Unit test case
test('should set range state', () => {
document.body.innerHTML = `<div id="twobuttons-range_0" class="twobuttons-range" data-technicalname="motor" data-unit="mm" data-min="100" data-max="500" data-start="[30,50]"></div>`;
Object.defineProperty(window.document, 'noUiSlider', {
set: jest.fn(),
});
popState();
expect($('#twobuttons-range_0').length).toBe(1);
});
I use it like this and it works:
Inside myComponent.tsx:
import noUiSlider from "nouislider";
....
globalSlider: noUiSlider.noUiSlider;
Inside myComponent.spec.tsx:
component = new myComponent();
...
it("Set value for slider", () => {
component.globalSlider = {
options: undefined, target: undefined, destroy(): void {
}, get(): string | string[] {
return undefined;
}, off(): void {
}, on(): void {
}, reset(): void {
}, updateOptions(): void {
}, set: jest.fn() };
component.setSliderValue("3");
});

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

How to mock chrome.cookies.getAll using Jasmine?

I have the following code in background.js
var allCookies = [];
function getAllCookies() {
chrome.cookies.getAll({}, function(cookies) {
for (var i in cookies) {
allCookies.push(cookies[i])
}
}
});
}
Now in specs.js, I have written the following code to test the getAllCookies method -
describe('getAllCookies', function() {
beforeEach(function() {
chrome = {
cookies: {
getAll : function() {
return [
'cookie1',
'cookie2'
]
}
}
};
spyOn(chrome.cookies,'getAll');
});
it('should updated global variable allCookies', function() {
getAllCookies();
expect(allCookies).toEqual(['cookie1','cookie2'])
})
})
But the test is failing as allCookies = [] but it should be equal to ['cookie1','cookie2']
Can someone please help me to mock such chrome APIs (say chrome.cookies.getAll) which takes callback function as argument?

Jasmine test complaining about 'undefined' is not an object

I have checked other questions similar to my problem. but this problem can apparently be different in every case.
Angular Jasmine Test complains
TypeError: 'undefined' is not an object (evaluating 'fields.forEach')at discoverDependentFields
Here is my discoverDependentFields function
discoverDependentFields($scope.response.Fields);
function discoverDependentFields(fields) {
fields.forEach(function (field) {
field.DependencyFieldEvaluated = '';
if (field.DependencyField) {
var foundFields = fields.filter(function (fieldToFind) { return fieldToFind.Name === field.DependencyField; });
if (foundFields.length === 1) {
field.DependencyFieldEvaluated = foundFields[0];
}
}
});
}
and in the test I have this bit
this.controller('MyController', {
'$scope': this.scope,
}
});
this.scope.response.Fields = [
{
Name: "UserIdentity",
Value: {
"FirstName": "John"
},
PropertyName: "User.Identity"
}
];
I use the value of field.DependencyFieldEvaluated in a function in a directive like this
function dependencyMet(field) {
var dependentField = field.DependencyFieldEvaluated;
var met = compareToDependencyValue(field, dependentField.Value);
return met;
}
I have no idea why it is complaining
If
discoverDependentFields($scope.response.Fields);
is a line in your controller, then you need to setup the $scope.response.Fields data before instantiating the controller. In other words, swap the order of operations in your test to be
this.scope = {};
// or maybe this.scope = $rootScope.$new()
this.scope.response = {
Fields: [{
Name: "UserIdentity",
Value: {
FirstName: "John"
},
PropertyName: "User.Identity"
}]
};
this.controller('MyController', {
$scope: this.scope,
});

Unit testing Angular filter which relies on service

I'm attempting to test a custom filter I've built. The issue I'm running into is that this filter relies on an asynchronous call through a service. Below is my relevant filter code first, then my current test:
.filter('formatValue', ['serverService', '_', function(serverService, _) {
var available = null;
var serviceInvoked = false;
function formatValue(value, code) {
var details = _.findWhere(available, {code: code});
if (details) {
return details.unitSymbol + parts.join('.');
} else {
return value;
}
}
getAvailable.$stateful = true;
function getAvailable(value, code) {
if (available === null) {
if (!serviceInvoked) {
serviceInvoked = true;
serverService.getAvailable().$promise.then(function(data) {
available = data;
});
}
} else {
return formatValue(value, code);
}
}
return getAvailable;
}])
test:
describe('filters', function() {
beforeEach(function() {
module('underscore');
module('gameApp.filters');
});
beforeEach(module(function($provide) {
$provide.factory('serverService', function() {
var getAvailable = function() {
return {
// mock object here
};
};
return {
getAvailable: getAvailable
};
});
}));
describe('formatValue', function() {
it('should format values', inject(function(formatValueFilter) {
expect(formatValueFilter(1000, 'ABC')).toEqual('å1000');
}));
});
});
The error I'm encountering when running my tests is:
TypeError: 'undefined' is not an object (evaluating 'serverService.getAvailable().$promise.then')
Your mock service needs to return a resolved promise. You can do this by injecting $q and returning $q.when(data)
However, I would think about refactoring this filter first. Filters are intended to be fast computations and probably should not be dependent on an asynchronous call. I would suggest moving your http call to a controller, then pass in the data needed to the filter.

Categories