expect() with no actual expectations - javascript

The Problem:
Recently, while reviewing our existing test codebase, I've noticed a dangerous kind of typo/mistake when expect() was used without the "matching" part:
expect(page.filters.fromDateLabel.getText(), "After");
I'm pretty sure toEqual() was meant to be used here:
expect(page.filters.fromDateLabel.getText()).toEqual("After");
The problem with this is that jasmine would not fail the expectation in this case (well, obviously because nothing was actually expected). And this gets us to a more serious problem - nothing was actually tested in a test case - it was passing with no expectations made. We were getting a false sense of what was tested.
The Question:
I want to catch these mistakes as fast as possible. How do you think I should handle the problem?
Thoughts:
somehow fail a test case if there was no expectations made in it (not sure if jasmine has anything like this built-in)
"patch" the expect() and issue a warning/raise an error if nothing was called on the "expect" part
use static code analysis - define a custom eslint rule

The custom ESLint rule provided in the answer is now a part of eslint-plugin-jasmine 1.6.0:
valid-expect
Old Answer:
Here is a custom ESLint rule I've ended up with:
module.exports = function (context) {
return {
// checking "expect()" arguments
CallExpression: function (node) {
if (node.callee.name === 'expect') {
if (node.arguments.length > 1) {
context.report(node, 'More than one argument passed to expect()')
} else if (node.arguments.length === 0) {
context.report(node, 'No arguments passed to expect()')
}
}
},
// nothing called on "expect()"
'CallExpression:exit': function (node) {
if (node.callee.name === 'expect' && node.parent.type === 'ExpressionStatement') {
context.report(node, 'Nothing called on expect()')
}
}
}
}
It checks for 3 things:
more than 1 argument passed to expect()
no arguments are passed to expect()
there was nothing called on expect()
Here are the sample invalid expect() usages it currently catches:
expect(page.filters.fromDateLabel.getText(), "After");
expect("After");
expect();
As for the option #1, there is actually a quite related and useful ESLint rule being already implemented and open-sourced by [eslint-plugin-jasmine]:
Enforce expectation (missing-expect)

I tend to think that the static analysis route is best, but if you’re looking for a quick and dirty way, here’s some code that grabs the expectations returned by all calls to expect and creates a proxy that tracks whether any of the expectation’s properties were ever used:
var unusedExpectations = new Set();
var originalExpect = window.expect; // Should be empty after every spec
var expect = function() {
var rawExpectation = originalExpect.apply(this, arguments);
unusedExpectations.add(rawExpectation); // Assume unused until used
// Traverse expectation and its prototypes, copying all properties to
// our proxy object. (Note that this becomes much simpler if you have
// ES6 Proxy in your environment.)
var proxy = {}
for(var proto = rawExpectation; proto; proto = proto.__proto__) {
Object.getOwnPropertyNames(proto).forEach(function(prop) {
if(Object.getOwnPropertyDescriptor(proxy, prop))
return;
Object.defineProperty(
proxy, prop, {
get: function() {
// Aha! Somebody used this expectation for _something_.
unusedExpectations.delete(rawExpectation);
return rawExpectation[prop];
}
}
);
});
}
return proxy;
}
Put that in a place where it hides Jasmine’s expect from your specs, and then:
beforeEach(function() {
unusedExpectations.clear();
});
afterEach(function() {
expect(unusedExpectations.size).toEqual(0);
});
Caveats:
Kind of evil.
Will not catch expect(foo).toBeFalsy; (missing parens).
Counts the use of any property, so won’t catch expect(foo).toString().
Still, it works!
One could add code to inspect the stack trace and extract the location of the offending expect(), but I imagine flagging which spec has an unused expect() is sufficient.

Related

Creating unit test with jasmine and getting an object as my second call. How do I call the function correctly?

I am very new to coding so please bear with me.
I am trying to write a test for this function:
redirect() {
if (!this.userInfo || !this.userInfo.userId) {
const originalPath = this.$location.path();
if (originalPath === '/') {
this.$location.path('/login');
} else {
this.$location.path('/login').search('redirect', originalPath);
}
return;
}
This is my mock:
beforeEach(() => {
mocks = {path: function () {return '/login';},
search: function() {return 'redirect', 'fakepath'}};
ctrl = new BxIndex(mocks);
});
This is my test:
spyOn(ctrl.$location, 'path').and.returnValue(mocks);
spyOn(ctrl.$location, 'search').and.callThrough();
ctrl.redirect();
expect(ctrl.$location.search).toHaveBeenCalledWith('redirect', 'fakepath');
It fails because the second call is an object:
Expected spy search to have been called with [ 'redirect', 'fakepath' ] but actual calls were [ 'redirect', Object({ path: spy on path, search: spy on search }) ].
The simple answer to get around the problem with this particular test is to change this line:
//spyOn(ctrl.$location, 'path').and.returnValue(mocks);
spyOn(ctrl.$location, 'path').and.returnValue('fakepath');
Edit: Looking at the code being tested, it looks like ctrl.$location.path returns different results depending on how it's called. If it's called with no parameters, then it appears to return a string. If it's called with a string as the parameter, then it appears to return the ctrl.$location object itself. Assuming I'm correct on that, there are two options...
The first option is to just set up your ctrl object for the test so that ctrl.$location.path doesn't need to be mocked (spied) at all, if possible.
The second option is to implement the dual-return logic in your spy using callFake:
spyOn(ctrl.$location, 'path').and.callFake(function(arg) {
if (typeof arg == "string") {
return ctrl.$location;
} else {
return "fakepath";
}
});
End Edit
But I think you're missing some details on how the spyOn functions work, so I will elaborate a little!
When you spy on a function, you're basically replacing that function with a spy - so if you subsequently call that function, it does nothing by default.
But you can add on to your spy too - for instance, you can have it return a specific value, or you can have it go ahead and call through to the original function that it is standing in for. That's what's happening when you use spyOn(...).and.returnValue() and spyOn(...).and.callThrough().
For example, let's say I have this simple object, foo. It has a function called sayHello, which calls another function called send, which sends a message over the network to Bob. If the message is sent successfully, the messagesToBob counter goes up by one, otherwise it remains the same. (send returns true if it worked, or false if it didn't).
// Begin contrived example!
var foo = {
messagesToBob: 0,
sayHello: function() {
if (this.send()) {
this.messagesToBob += 1;
}
},
send: function() {
network.send("Bob", "Hello");
}
};
Now I want to test this, but obviously I don't want to be sending Bob a bunch of messages during my tests. So that's where the spy comes in.
spyOn(foo, "send");
This essentially takes the foo.send function and replaces it with an empty function (I believe it will just returned undefined).
If I want to test the foo increments the counter on a successful run, I can make the "send" spy return true:
spyOn(foo, "send").and.returnValue(true);
foo.sayHello();
expect(foo.messagesToBob).to.equal(1);
If I want to test that it does not increment on a failed send, I can make the "send" spy return false.
In any of those cases, I could also check the status of the spy function itself using the expect(...).toHaveBeenCalled() or .toHaveNotBeenCalled() (or whatever functions are available to your particular testing framework/environment).
Anyway, I hope that helps! Your "mock" object's functions don't need to return a value in this example (assuming you need that object at all), because those functions are being replaced with spies anyway.

How to properly stub a function return value?

Premise: JS ES6, NodeJS
Testing Framework: TAP
Mocking Library: testdouble.js
I am attempting to mock the return value for the method of my class and keep receiving this error:
not ok Unsatisfied verification on test double. Wanted: - called with (true). But there were no invocations of the test double.
Here is my testing code:
// Imports for unit testing
const tap = require('tap');
const Subject = require('../src/iTunesClient.js');
const td = require('testdouble');
let reqJson;
// Ensure the iTunes class methods are called
tap.test('iTunesClient class methods function as intended', (t) => {
t.beforeEach((ready) => {
reqJson = td.replace('../src/reqJson.js');
ready();
});
t.afterEach((ready) => {
td.reset();
ready();
});
t.test('iTunesClient.getData', (assert) => {
const callback = td.function();
const subject = new Subject();
subject.setTerm('abc 123');
subject.setURL();
td.when(reqJson.get(td.callback)).thenCallback(true);
subject.getData(callback);
td.verify(callback(true));
assert.end();
});
t.end();
});
Specifically, this line is related to my issue:
td.verify(callback(true));
How can I fake the callback value of true for reqJson.get()? Right now, Subject.geData() is a method of the iTunesClient class which calls another file, reqJson.js, to use its exported get() method.
It's a little hard to tell from your example, but it looks like you're requiring iTunesClient before you call td.replace. In this case, the real reqJson module will be required and cached on line 3.
You need to call td.replace early enough to avoid this, e.g. in between requiring tap and iTunesClient.
I wanted to update this question, as I recently solved this issue. Essentially, I had two issues:
Account for both reqJson function parameters
Account for all callback return values
Per testdouble documentation for item 1:
When passed td.matchers.anything(), any invocation of that test double function will ignore that parameter when determining whether an invocation satisfies the stubbing.
Hence, I adjusted my line of code as follows:
Before: td.when(reqJson.get(td.callback)).thenCallback(true);
After: td.when(reqJson.get(td.matchers.anything(), td.callback)).thenCallback(null, null, null);

Returning a function for the outside function in Javascript

I don't know this is possible, but I have some special situations requiring it.
//Obj is a class with nothing.
Obj.prototype.v1 = function(){
//this is a normal statement.
//it could be something else
return 3;
}
//or it can be any way to declare a function:
var v1 = function(){return 3};
Obj.prototype.v2 = function(){
return this.v1()+2;
}
How to make it directly returns 3 here? It's like the function v1() is something like pseudocode this.return(3) for v2(), and certainly nothing can be reached after the first return.
If I'm generating the code dynamically and it has to be a return in the second function. (So it can easily get unexpected token for return (return 3).v2(), while trying to get the inside function to be called behaving like it's part of current function.)
Is there anyway to make this.v1() directly cause outside function v2() to return, for the first return it encounters? Preferably by focusing on modifying v1().
Is there anyway to make this.v1() directly cause outside function v2() to return, for the first return it encounters?
The idiomatic solution is to express this logic in v2. For example, you could cause v1 to modify a flag that decides what v2 does:
Obj.prototype.v1 = function(){
this.v1.continue = true; /* XXX: Continue? */
this.v1.continue = false; /* ... or not? */
return 3;
}
Obj.prototype.v2 = function(){
var ret_val = this.v1()+2;
if (!this.v1.continue) {
return;
}
/* XXX: Insert more code here */
}
We're talking about rather basic JavaScript here. Do you have a book?
Preferably by focusing on modifying v1().
I'm sure it's possible to circumvent the control of execution that v2 has when v1 returns in some situations, but that doesn't make it a good idea. Think about how difficult it'll become to debug this code!
For example, you could throw an error which v2 doesn't catch, and catch it further upstream. Such a hideous abuse of throw would be worse than the abuse of goto! Don't modify your code flow in such an unclear manner; it makes maintenance and debugging a nightmare!

What is the current best way to wrap console.log() that will preserve line numbers?

I've previously used the following based on other SO answers (without really understanding the need for (nor the workings of) the prototype.apply.apply
var mylogger = {
log: function () {
if (window.console) {
if (window.console.log) {
Function.prototype.apply.apply(console.log, [console, arguments]);
}
}
},
...
};
while this prevents IE from crapping on itself, it also make the line number reporting unusable (it always reports the apply.apply.. line.
I was playing around a little and discovered that the following seems to do exactly what I need, i.e. prevent IE from crapping on itself and report the line number where mylogger.log(..) was called from..
var mylogger = {
// function invocation returning a safe logging function..
log: (function () {
if (window.console && window.console.log && Function.prototype.bind) {
return window.console.log.bind(window.console);
} else {
return function () {};
}
}()),
...
};
I've done some basic testing on IE/FF/Chrome without seeing any issues.. is this reasonable or is there a better way?
What you're doing is fine I guess, but if you aren't adding any additional functionality, you could do something simple and in one line:
window.console = (window.console || {debug:function(){},log:function(){},error:function(){}});
You could, of course, add other console functions if you use them.

What is “assert” in JavaScript?

What does assert mean in JavaScript?
I’ve seen something like:
assert(function1() && function2() && function3(), "some text");
And would like to know what the method assert() does.
There is no standard assert in JavaScript itself. Perhaps you're using some library that provides one; for instance, if you're using Node.js, perhaps you're using the assertion module. (Browsers and other environments that offer a console implementing the Console API provide console.assert.)
The usual meaning of an assert function is to throw an error if the expression passed into the function is false; this is part of the general concept of assertion checking. Usually assertions (as they're called) are used only in "testing" or "debug" builds and stripped out of production code.
Suppose you had a function that was supposed to always accept a string. You'd want to know if someone called that function with something that wasn't a string (without having a type checking layer like TypeScript or Flow). So you might do:
assert(typeof argumentName === "string");
...where assert would throw an error if the condition were false.
A very simple version would look like this:
function assert(condition, message) {
if (!condition) {
throw message || "Assertion failed";
}
}
Better yet, make use of the Error object, which has the advantage of collecting a stack trace and such:
function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
}
If using a modern browser or nodejs, you can use console.assert(expression, object).
For more information:
Chrome API Reference
Firefox Web Console
Firebug Console API
IE Console API
Opera Dragonfly
Nodejs Console API
The other answers are good: there isn't an assert function built into ECMAScript5 (e.g. JavaScript that works basically everywhere) but some browsers give it to you or have add-ons that provide that functionality. While it's probably best to use a well-established / popular / maintained library for this, for academic purposes a "poor man's assert" function might look something like this:
const assert = function(condition, message) {
if (!condition)
throw Error('Assert failed: ' + (message || ''));
};
assert(1 === 1); // Executes without problem
assert(false, 'Expected true');
// Yields 'Error: Assert failed: Expected true' in console
assert() is not a native javascript function. It is a custom function someone made. You will have to look for it on your page or in your files and post it for anybody to help determine what it's doing.
check this:http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-quick-and-easy-javascript-testing-with-assert/
it is for testing JavaScript. Amazingly, at barely five or six lines, this code provides a great level of power and control over your code, when testing.
The assert function accepts two parameters:
outcome: A boolean, which references whether your test passed or failed
description: A short description of your test.
The assert function then simply creates a list item, applies a class of either “pass” or “fail,” dependent upon whether your test returned true or false, and then appends the description to the list item. Finally, that block of coded is added to the page. It’s crazy simple, but works perfectly.
If the assertion is false, the message is displayed. Specifically, if the first argument is false, the second argument (the string message) will be be logged in the developer tools console. If the first argument is true, basically nothing happens. A simple example – I’m using Google Developer Tools:
var isTrue = true;
var isFalse = false;
console.assert(isTrue, 'Equals true so will NOT log to the console.');
console.assert(isFalse, 'Equals false so WILL log to the console.');
It probably came with a testing library that some of your code is using. Here's an example of one (chances are it's not the same library as your code is using, but it shows the general idea):
http://chaijs.com/guide/styles/#assert
Word or function "assert" is mostly used in testing parts of application.
Assert functions are a short way of instructing the program to check the condition (also called "assertion") and if the condition is not True, it will throw error.
So let's see how it would look like in "normal code"
if (typeof "string" === "array") {
throw Error('Error: "string" !== "array"');
}
With assert you can simply write:
assert(typeof "string" === "array")
In Javascript, there's no native assert function, so you have to use one from some library.
For simple introduction, you can check this article:
http://fredkschott.com/post/2014/05/nodejs-testing-essentials/
I hope it helps.
Assertion throws error message if first attribute is false, and the second attribute is the message to be thrown.
console.assert(condition,message);
There are many comments saying assertion does not exist in JavaScript but console.assert() is the assert function in JavaScript
The idea of assertion is to find why/where the bug occurs.
console.assert(document.getElementById("title"), "You have no element with ID 'title'");
console.assert(document.getElementById("image"), "You have no element with ID 'image'");
Here depending on the message you can find what the bug is.
These error messages will be displayed to console in red color as if we called console.error();
You can use assertions to test your functions eg:
console.assert(myAddFunction(5,8)===(5+8),"Failed on 5 and 8");
Note the condition can be anything like != < > etc
This is commonly used to test if the newly created function works as expected by providing some test cases and is not meant for production.
To see more functions in console execute console.log(console);
In addition to other options like console.assert or rolling your own, you can use invariant. It has a couple of unique features:
It supports formatted error messages (using a %s specifier).
In production environments (as determined by the Node.js or Webpack environment), the error message is optional, allowing for (slightly) smaller .js.
Java has a assert statement, the JVM disables assertion validation by default. They must be explicitly enabled using command line argument -enableassertions (or its shorthand -ea),
while JavaScript supports console.assert(), it's just a logging method and won't interrupt current procedure if assertion failed.
To bring things together and satisfy various needs, here is a tiny js assertion lib.
globalThis.assert = (()=> {
class AssertionError extends Error {
constructor(message) {
super(message);
this.name = 'AssertionError';
}
}
let config = {
async: true,
silent: false
};
function assert(condition, message = undefined) {
if (!condition) {
if (config.silent) {
//NOOP
} else if (config.async) {
console.assert(condition, message || 'assert');
} else {
throw new AssertionError(message || 'assertion failed');
}
}
}
assert.config = config;
return assert;
})();
/* global assert */
Object.assign(assert.config, {
// silent: true, // to disable assertion validation
async: false, // to validate assertion synchronously (will interrupt if assertion failed, like Java's)
});
let items = [
{id: 1},
{id: 2},
{id: 3}
];
function deleteItem(item) {
let index = items.findIndex((e)=> e.id === item.id);
assert(index > -1, `index should be >=0, the item(id=${item.id}) to be deleted doesn't exist, or was already deleted`);
items.splice(index, 1);
}
console.log('begin');
deleteItem({id: 1});
deleteItem({id: 1});
console.log('end');
Node.js has an assert function you can import:
const assert = require('assert')
It works as one would expect, in that assert(false) throws an error, and assert(false, message) throws an error with a message.
The other answers have already pointed out that JS itself has no native assert function, and that remains true as of this writing (April 2021).
Previous answers can be improved in terms of performances and compatibility.
Check once if the Error object exists, if not declare it :
if (typeof Error === "undefined") {
Error = function(message) {
this.message = message;
};
Error.prototype.message = "";
}
Then, each assertion will check the condition, and always throw an Error object
function assert(condition, message) {
if (!condition) throw new Error(message || "Assertion failed");
}
Keep in mind that the console will not display the real error line number, but the line of the assert function, which is not useful for debugging.
If you use webpack, you can just use the node.js assertion library. Although they claim that it's "not intended to be a general purpose assertion library", it seems to be more than OK for ad hoc assertions, and it seems no competitor exists in the Node space anyway (Chai is designed for unit testing).
const assert = require('assert');
...
assert(jqXHR.status == 201, "create response should be 201");
You need to use webpack or browserify to be able to use this, so obviously this is only useful if those are already in your workflow.
As mentioned by T.J., There is no assert in JavaScript.
However, there is a node module named assert, which is used mostly for testing. so, you might see code like:
const assert = require('assert');
assert(5 > 7);
assert() is the assert function in JavaScript. The main idea of assertion is to find why/where the bug occurs.
Chrome devtools support console.assert
You can open devtools and create a snippet in devtools-source-navigator-Snippets. And code some code... and run the snippet...

Categories