I have an Angular module, validation, which is declared like so:
(function(ns){
ns.validation = angular.module("validation", []);
})(blog);
The module contains two services, validator and validationRulesProvider, which look like:
(function(module){
module
.factory("validationRulesProvider", function(){
var _getRules = function() {
return [{
isValid: function(post) {
return post.length > 0;
}
}];
};
return {
getRules: _getRules
};
});
})(blog.validation);
and
(function(module){
module
.factory("validator", ["validationRulesProvider", function(validationRulesProvider){
var _validate = function(post) {
var rules = validationRulesProvider.getRules();
for (var rule in rules) {
if (!rule.isValid(post)) {
return false;
}
}
return true;
};
return {
validate: _validate
};
}]);
})(blog.validation);
I am attempting to test (using Jasmine) that
The getRules method on validationRulesProvider is actually called from the validate method
The post parameter is run through each rule returned from said method
I have the following Jasmine test script:
describe("Validator: ", function(){
var _validator;
var _mockedValidationRulesProvider;
var _mockRule;
beforeEach(function(){
module("validation");
inject(function(validationRulesProvider){
_mockedValidationRulesProvider = validationRulesProvider;
});
_mockRule = jasmine.createSpy();
spyOn(_mockedValidationRulesProvider, "getRules")
.and
.returnValue([{
isValid: _mockRule
}]);
inject(function(validator){
_validator = validator;
});
});
describe("getRules - ", function(){
it("gets a collection of rules from the rules provider", function(){
_validator.validate("");
expect(_mockedValidationRulesProvider.getRules).toHaveBeenCalled();
});
it("should pass the post through each rule received from the rules provider", function(){
expect(_mockRule.calls.count()).toEqual(_mockedValidationRulesProvider.getRules().length);
});
});
});
So, I'm simply trying to create a fake implementation of validationRulesProvider.getRules. My trouble is that both of these tests fail. If I alter the line:
spyOn(_mockedValidationRulesProvider, "getRules")
.and
.returnValue([{
isValid: _mockRule
}]);
to simply be
spyOn(_mockedValidationRulesProvider, "getRules")
.and
.returnValue([]);
then the first of the two tests pass, as the loop in validator.validate will never be entered.
Karma gives the following output:
PhantomJS 1.9.8 (Windows 7) Validator: getRules - gets a collection of rules from the rules provider FAILED
TypeError: 'undefined' is not a function (evaluating 'rule.isValid(post)')
at C:/Users/User/JS/Angular/Learning/blogsite/scripts/validation/validator.js:8
at C:/Users/User/JS/Angular/Learning/blogsite/scripts/tests/validator.test.js:32
PhantomJS 1.9.8 (Windows 7) Validator: getRules - should pass the post through each rule received from the rules provider FAILED
Expected 0 to equal 1.
at C:/Users/User/JS/Angular/Learning/blogsite/scripts/tests/validator.test.js:37
PhantomJS 1.9.8 (Windows 7): Executed 5 of 5 (2 FAILED) (0 secs / 0.039 secs)
I'm a bit of a loss as to why the tests are failing in the first instance because it seems like what I should be returning from the spy is an array of one object which contains an "isValid" function - which is exactly what is returned from the actual implementation of that function.
What am I doing wrong?
When you want to loop an array in javascript, you need to use javascript basic 'for loop' instead of 'for-in loop'.
The code in your validator factory should be like this.
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (!rule.isValid(post)) {
return false;
}
}
Related
I am working on angular app automation where I have written a common function which needs to called at multiple places in my tests to get the form id which is generating dynamically. This function is always returning me {[[PromiseStatus]]: "pending"}"] error.
CommonFunctions.js
var common_functions = function() {
this.getFormId = function() {
// var url = "http://localhost/test/#/submission/207/form/1976";
return browser.getCurrentUrl().then(function(url){
return url.substr(-4);
});
}
};
Now this function is being used in pageobject file to get the element locator.
PageObject file:
var general_information = function() {
this.street1 = function(field1) {
var formid = common_functions.getFormId();
var street="fieldValue.street1_".concat(formid);
element(by.model(street)).sendKeys(field1);
}
Finally when i invoke this function in my test script, I get error.
Test script:
it("Verify that user is able to fill values in general information", function(){
general_information.street1('Street Victoria');
general_information.zipCode('1004');
});
Exception appearing:
Failed: invalid element state: Failed to execute 'querySelectorAll' on 'Document': '[ng-model="fieldValue.street1_Promise::1833 {[[PromiseStatus]]: "pending"}"]' is not a valid selector.
(Session info: chrome=50.0.2661.94)
(Driver info: chromedriver=2.21.371459 (36d3d07f660ff2bc1bf28a75d1cdabed0983e7c4),platform=Windows NT 6.1 SP1 x86_64)
Can somebody help me with this where I am doing it wrong?
That's because the getFormId() function returns a promise. And, since you need a value into which a promise is resolved, you need to use .then():
this.street1 = function(field1) {
common_functions.getFormId().then(function (formid) {
var street = "fieldValue.street1_".concat(formid);
element(by.model(street)).sendKeys(field1);
});
}
I have some code that exercises the “invalid values” setting on an element range index. In this case, I have configured a dateTime element range index on the onDate element in my database (which will apply to both XML elements and JSON properties). I’ve set that index to reject invalid values. This setting means if I try to set the value of an onDate element and it is not castable to a dateTime or is null (literal null in JSON or xsi:nil="true" in XML), my update will fail. (The opposite behavior is to completely ignore invalid values.)
I tried the following code in Server-Side JavaScript in MarkLogic 8.0-4:
'use strict';
declareUpdate();
var errors = [];
var inputs = {
'/37107-valid.json': (new Date()).toISOString(),
'/37107-invalid.json': 'asdf', // Should throw an error
'/37107-null.json': null
};
for(var uri in inputs) {
try {
xdmp.documentInsert(
uri,
{ 'onDate': inputs[uri] },
xdmp.defaultPermissions(),
['37107'] // Collections
);
} catch(err) {
errors.push(err);
}
}
errors.length;
I would have expected my request to succeed and to end up with 1 === errors.length, because only the second insert would have failed because 'asdf' is not castable as a dateTime and it is not null. However, instead I get an XDMP-RANGEINDEX error and my transaction fails. Why doesn’t my try/catch work here?
The issue is how MarkLogic processes update transactions. Rather than actually changing the data with each xdmp.docuentInsert(…) call, MarkLogic queues up all of the updates and applies them atomically at the end of the request. (This is also why you can’t see database updates within the same transaction.) Thus, the error isn’t being thrown until after the loop has executed and the database tries to commit the queued transactions. This behavior is the same in XQuery (slightly simplified):
let $uris := (
'/37107-valid.xml',
'/37107-invalid.xml',
'/37107-null.xml'
)
let $docs := (
<onDate>{fn:current-dateTime()}</onDate>,
<onDate>asdf</onDate>,
<onDate xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
)
return
for $uri at $i in $uris
return
try {
xdmp:document-insert($uri, $docs[$i], (), ('37107'))
} catch($err) {
xdmp:log($err)
}
In order to catch the errors synchronously, you’d need to put each update into its own transaction. In general, this approach will be much slower and resource intensive than MarkLogic’s default transaction handling. However, it’s illustrative here to demonstrate what’s happening under the covers and can come in handy for specific use cases, like this one.
In the example below, I use xdmp.invokeFunction() to “call” a function in a separate transaction from the parent request. (First-class functions for the win!) This allows the updates to be fully applied (or rolled back with an error) and the calling module to see the updates (or errors). I’ve wrapped the low-level xdmp.invokeFunction() in my own applyAs() function to provide some niceties, like correctly passing function arguments to the curried function.
'use strict';
var errors = [];
var inputs = {
'/37107-valid.json': (new Date()).toISOString(),
'/37107-invalid.json': 'asdf',
'/37107-null.json': null
};
var insert = applyAs(
function(uri, value) {
return xdmp.documentInsert(
uri,
{ 'onDate': inputs[uri] },
xdmp.defaultPermissions(),
['37107']
);
},
{ isolation: 'different-transaction', transactionMode: 'update' },
'one'
);
for(var uri in inputs) {
try {
insert(uri, inputs[uri]);
} catch(err) {
errors.push(err);
}
}
errors.length; // Correctly returns 1
// <https://gist.github.com/jmakeig/0a331823ad9a458167f6>
function applyAs(fct, options, returnType /* 'many', 'one', 'iterable' (default) */) {
options = options || {};
return function() {
var params = Array.prototype.slice.call(arguments);
// Curry the function to include the params by closure.
// xdmp.invokeFunction requires that invoked functions have
// an arity of zero.
var f = (function() {
return fct.apply(null, params);
}).bind(this);
// Allow passing in user name, rather than id
if(options.user) { options.userId = xdmp.user(options.user); delete options.user; }
// Allow the functions themselves to declare their transaction mode
if(fct.transactionMode && !(options.transactionMode)) { options.transactionMode = fct.transactionMode; }
var result = xdmp.invokeFunction(f, options); // xdmp.invokeFunction returns a ValueIterator
switch(returnType) {
case 'one':
// return fn.head(result); // 8.0-5
return result.next().value;
case 'many':
return result.toArray();
case 'iterable':
default:
return result;
}
}
}
I'm trying to unit test a situation with postMessage and addEventListener. The use case is that I use a separate window for user logins similar to the OAuth workflow and then use postMessage in the login window to notify the main window that the user has logged in. This is the listening code that would be in the main window:
$window.addEventListener("message", function(event) {
if (event.data.type === "authLogin") {
service.curUser = event.data.user;
utilities.safeApply($rootScope);
$window.postMessage({type: "authLoginSuccess"}, '*');
}
});
The utilities.safeApply function is defined as:
// Run $apply() if not already in digest phase.
utilitiesService.safeApply = function(scope, fn) {
return (scope.$$phase || scope.$root.$$phase) ? scope.$eval(fn) : scope.$apply(fn);
};
My unit test is designed to send a postMessage to simulate the login:
describe('auth login postMessage', function() {
var testUser = {handle: 'test'};
beforeEach(function(done) {
$window.postMessage({type: 'authLogin', user: testUser}, '*');
function onAuthLoginSuccess(event) {
$window.removeEventListener('message', onAuthLoginSuccess);
done();
}
$window.addEventListener("message", onAuthLoginSuccess);
});
it("should set the user object", function() {
expect(service.curUser).toEqual(testUser);
});
});
This is the result of running the unit test:
12 09 2015 14:10:02.952:INFO [launcher]: Starting browser Chrome
12 09 2015 14:10:05.527:INFO [Chrome 45.0.2454 (Mac OS X 10.10.5)]: Connected on socket 537CxfI4xPnR0yjLAAAA with id 12583721
Chrome 45.0.2454 (Mac OS X 10.10.5) ERROR
Uncaught Error: Unexpected request: GET security/loginModal.tpl.html
No more request expected
at http://localhost:8089/__test/Users/userX/esupport/code/proj/public/vendor/angular-mocks/angular-mocks.js:250
Chrome 45.0.2454 (Mac OS X 10.10.5): Executed 23 of 100 (skipped 60) ERROR (0.333 secs / 0.263 secs)
I'm at a loss of why it would try to load an HTML template. I've narrowed it down so that if I don't call the $scope.$apply() function, the unit test succeeds without error. However, I need that $apply() to update the view.
I've tried stubbing out the utilities.safeApply method in the unit test and also tried to set up an expectation for the HTML template GET request. These attempts look like:
describe('auth login postMessage', function() {
var testUser = {handle: 'test'};
beforeEach(function(done) {
$httpBackend.when('GET', 'security/loginModal.tpl.html').respond(''); // <-- NEW
spyOn(utilities, 'safeApply').and.callFake(angular.noop); // <-- NEW
window.postMessage({type: 'authLogin', user: testUser}, '*');
function onAuthLoginSuccess(event) {
window.removeEventListener('message', onAuthLoginSuccess);
done();
}
window.addEventListener("message", onAuthLoginSuccess);
});
it("should set the user object", function() {
expect(service.curUser).toEqual(testUser);
});
});
Both of these attempts do nothing. I still get the same error message. I've tried other debugging steps such as using spyOn for $location.path() so it returns a sample value like "/fake". In all other unit tests where I'm directly testing service methods and not using postMessage to trigger the service code, the stubbing works fine. However, in the addEventListener function, the $location.path() returns "" which points towards a theory that the addEventListener function is running in an entirely different instance than what the unit test has prepared. That would explain why the stubbed out functions aren't being used and why this other instance is trying to load a bogus template. This discussion also solidifies the theory.
So now the question is, how do I get it to work? i.e, how do I get the addEventListener function to run in the same instance where my stubbed functions are used and it doesn't make a request to the HTML template?
I'd just mock all the external parts and make sure the end result is what you expect.
Looking at your gist, you may need to mock some more services but this should be enough to test the "authLogin" message event.
describe('some test', function() {
var $window, utilities, toaster, securityRetryQueue, service, listeners;
beforeEach(function() {
module('security.service', function($provide) {
$provide.value('$window',
$window = jasmine.createSpyObj('$window', ['addEventListener', 'postMessage']));
$provide.value('utilities',
utilities = jasmine.createSpyObj('utilities', ['safeApply']));
$provide.value('toaster',
toaster = jasmine.createSpyObj('toaster', ['pop']));
$provide.value('securityRetryQueue',
securityRetryQueue = jasmine.createSpyObj('securityRetryQueue', ['hasMore', 'retryReason']));
// make sure you're not fetching actual data in a unit test
securityRetryQueue.onItemAddedCallbacks = [];
securityRetryQueue.hasMore.and.returnValue(false);
$window.addEventListener.and.callFake(function(event, listener) {
listeners[event] = listener;
});
});
inject(function(security) {
service = security;
});
});
it('registers a "message" event listener', function() {
expect($window.addEventListener).toHaveBeenCalledWith('message', listeners.message, false);
});
it('message event listener does stuff', inject(function($rootScope) {
var event = {
data: {
type: 'authLogin',
user: 'user'
},
stopPropagation: jasmine.createSpy('event.stopPropagation')
};
listeners.message(event);
expect(service.curUser).toBe(event.data.user);
expect(toaster.pop).toHaveBeenCalledWith('success', 'Successfully logged in.');
expect(utilities.safeApply).toHaveBeenCalledWith($rootScope);
expect($window.postMessage).toHaveBeenCalledWith({type: "authLoginSuccess"}, '*');
expect(event.stopPropagation).toHaveBeenCalled();
}));
});
I have the following (simplified) javascript module which uses jQuery Cookie plugin to check if cookies are enabled. If cookies are disabled it warns the user:
var cookiePolicy = (function () {
var cookiesEnabled = function () {
return $.cookie('check', 'valid', { expires: 1 }) && $.cookie('check') == 'valid';
};
return {
updateCookiePolicy: function () {
if (!cookiesEnabled()) {
$("#cookie-policy").append('<p id="cookie-warning">Cookies are disabled. Some features of this site may not work correctly.</p>');
}
}
};
})();
I have the following unit test:
QUnit.test("When cookies are enabled the cookie policy text remains unchanged", function (assert) {
sinon.mock($).expects("cookie").once().withExactArgs("check", "valid", { expires: 1 });
sinon.mock($).expects("cookie").once().withExactArgs("check").returns("valid");
cookiePolicy.updateCookiePolicy();
assert.equal(0, $('#cookie-warning').length, "Failed!");
});
The test fails because "cookie is already wrapped". I assume this is because I am mocking $.cookie for both set and read. How can I mock the call to $.cookie for both setting and reading in this test?
Your assumption is correct. Depending on the version of Sinon you're using, you could do something like this:
// UUT
var foo = {
bar: function() {}
};
// Test setup
var mock = sinon.mock(foo);
var expectation = mock.expects('bar').twice();
expectation.onFirstCall().stub.calledWithExactly('baz');
expectation.onSecondCall().stub.calledWithExactly('qux');
// Test
foo.bar('baz');
foo.bar('qux');
mock.verify();
BTW, it's strange to use Sinon mocks without using .verify(). Maybe stubs would be a better fit?
I am trying to run unit tests against every item in a collection using Vows.js and I'm having a heck of a time getting it to work. Here is what I have at the moment.
'the variations objects': {
topic: function() {
var promise = new(events.EventEmitter),
variations = JSON.parse(body).variations;
for(var i = variations.length - 1; i >= 0; i--) {
promise.emit("success", variations[i]);
};
return promise;
},
'should have an x': function(topic) {
should.exist(topic.x);
},
'should have an action on the add_to_cart object if the product is IN_STOCK': function(topic) {
if(topic.x.id === 'TEST'){
should.exist(topic.x.action)
}
}
}
This seems like it is working. However, when I run the tests, I get 34 passed and 1 error. Vows does not indicate what test is erroring. I don't feel like using an EventEmitter is the right choice for this, but I'm not sure how else to have a new topic for each object in a given collection.