Creating integration tests in Ember 2.16 that utilize window.confirm()? - javascript

I am writing integration tests for an Ember 2.16 component and I am testing some user actions.
One of the user actions calls window.confirm(), where the user is asked if they are sure they want to delete an item before the item is deleted.
I want to test the functionality of this component, both with accepting and declining the confirm. The component action looks similar to this:
delete(id){
if(confirm('Are you sure you want to delete?')){
//do stuff
} else {
//do other stuff
}
}
Inside my integration tests I am successfully clicking the button to bring up the prompt, but I am running into this error:
[Testem] Calling window.confirm() in tests is disabled, because it causes testem to fail with browser disconnect error.
How can I create an integration test that will bypass the window.confirm() functionality?
I have added in my component a way to bypass the confirm if the env is in "test" mode, but this does not really help as I am not testing the portion of code that relies on the window.confirm().
I have looked around to see if there is a variable I can pass to the component to make the window.confirm() true/false, but have been unsuccessful.
How can I create a test that will test a component that calls window.confirm() inside an action?

One solution would be to save the original implementation of window.confirm and write your own implementation before your test, then restore the original implementation at the end of the test.
This is how I would do it:
// Watch out, this test is written with the latest ember-qunit syntax which might not be exactly what you have in your Ember 2.16 application
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from 'ember-test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('your component integration tests', function(hooks) {
setupRenderingTest(hooks);
test('clicking the OK confirm button', async function(assert) {
// save the original window.confirm implementation
const originalWindowConfirm = window.confirm;
// simulate the OK button clicked
window.confirm = function() { return true;}
// ADD YOUR TEST AND ASSERTIONS HERE
// restore the original window.confirm implementation
window.confirm = originalWindowConfirm;
});
});

I would stub window.confirm() in the test with a lib like sinon where I expect it to be called so that:
hopefully that error message won't occur
I know confirm() is actually called by the code and does what I
want it to do exactly (i.e., I can make it a simple fn)
it can be restored so the warning message will be logged in other
tests (which is helpful)
According to the testem code it overwrites window.confirm() to print this warning message:
window.confirm = function() {
throw new Error('[Testem] Calling window.confirm() in tests is disabled, because it causes testem to fail with browser disconnect error.');
};
So doing something like this in the test with sinon should work:
const confirm = sinon.stub(window, "confirm").callsFake(() => {
// fake implementation here which overwrites the testem implementation
});
// rest of the test
confirm.restore(); // restores the testem implementation

Related

How do I test a void module for a logger feature using Jest

I have been trying to read up on writing test methods for void modules where there is a side-effect.
I can't seem to wrap my head around implementing it in my scenario where I have a module that takes in an array of loggers. This is to allow the client to implement multiple sources of logging in particular scenarios like using the console and logging to a persisted logging store (multiple sources if need be).
I have a level of abstraction which maps through concrete sources with the same contract.
The class abstracting from concrete classes has some logic to know which methods to be called and I want to test that that logic is sound but I am not sure how to do so. Since these methods are void am I right in thinking that I may need to call .toHaveBeenCalled to check for these side-effects?
Initialize logger
src/index.js
import logger from "./logger";
logger.init([console]);
logger.debug("Hello debug");
Logger
src/logger.js - abstraction expecting an array of concretes
function init(loggers) {
...
}
function debug(message) {
writeToLogs(loggers, message, "debug");
}
function writeToLogs(loggers, message, type) {
// I want to test this logic - multiple loggers and different calls mapped correctly
loggers.forEach(logger => {
switch (type) {
case "debug":
logger.debug(message);
break;
...
}
}
}
module.exports = {
init: init,
debug: debug,
...
}
Console logger
src/logger/console.js - example of a concrete
function debug(message) {
console.debug(message);
}
module.exports = {
debug: debug,
...
}
Test
tests/logger.test.js
import logger from "../../src/logger";
test("logger writes information", () => {
// What should I do here?
});
The console module is just writing to the console. In my opinion I don't think it is necessary to be tested unless it changes in the future. If I were to test it, how do I verify that the console has been written to? I would see this as an integration test as it is the actual implementation that integrates with the browsers' console.
I am using webpack 4.6.0 on node 9.5.0 with jest 22.4.3.
console is a side effect you have no control over by just mocking an import as it is not imported but an object in the global namespace of your module. Fortunately you can overwrite this from outside using the global object in your test file. So you can easily set anything you want in there
const debug = jest.fn()
const log = jest.fn()
const error = jest.fn()
global.console = {debug, error, log}
Now everytime console.log in your src/logger/console.js is called it will use the spy were you later on can test that it was called.
so now whenever

Global function not available in Window object when using Jasmine & Headless Chrome

I'm having a hard time figuring out how I can access a function that is usually available as a method of the window object in the browser. I'm using Karma, Headless Chrome and Jasmine.
Here is a simplification of my issue:
I have a module called add-numbers-together.js:
function addTogether(a, b) {
return a + b;
}
(function(){
addTogether(2,5);
})();
That is being tested by this Jasmine test:
describe('add-numbers-together.js', function() {
it('Should add numbers together', function() {
require('module/add-numbers-together');
console.info(window.addTogether);
});
});
The require statement is definitely retrieving the module ok, I have tested that.
I was expecting the console.info to print out the definition of the function, as is the case when I do this in an actual browser, but instead this console.info returns undefined. Why is this? and how can I access the addTogether function after the module has been required in the test?
I'm guessing this is some quirk of Jasmine or Headless Chrome but I can't find the answer anywhere no matter how hard I search!
Also, please note: I do not want to add any test code to the module itself, that is not an option for me.
I changed the test slightly to check that the function is not undefined, but it failed regardless of Chrome or ChromeHeadless as the browser.
describe('add-numbers-together.js', function() {
it('Should add numbers together', function() {
require('module/add-numbers-together');
expect(window.addTogether).not.toBeUndefined();
});
});
To get a version of this test passing, you have several options:
Make the code under test a real module and export the function, then change the test to import that module (recommended). This can also be define and require if using AMD.
Configure your build setup to force a global export from that module without changing the code (webpack can do this, and it's kind of hackey, but it works if you can't edit that script). This is usually only done with 3rd party scripts.
Put the script content in the test file above the test code (not recommended, since you are copy-pasting and changes won't be synced up).
Load the script under test and then the test in the fixture markup (works, but kind of lame since your test fixture HTML now hard codes a script reference).
Here is the code under test rewritten for option #1 with the export of the function.
export function addTogether(a, b) {
return a + b;
}
(function () {
addTogether(2, 5);
})();
And here is the test rewritten for option #1 with the import of the module under test.
import * as addNumbersTogether from 'add-numbers-together';
describe('add-numbers-together.js', function () {
it('Should add numbers together', function () {
expect(addNumbersTogether.addTogether).not.toBeUndefined();
});
});

How do I access information about the currently running test case from the beforeEach function?

Using Protractor 5.1.2 and Jasmine2 for describing test cases, how does one get the current testcase/spec being run in the beforeEach method?
I would like to do some different setup based on which test case I'm running. I do not want to put these tests in different spec files with repeating code except for the little bit I want to change in the setup.
Example of what I'm looking for:
...
beforeEach(() => {
if(currentSpec/TestCase.name == "thisName") {
// Do a particular login specific to testcase.name
} else {
// Do a default login
}
});
...
My research into this brought up much older solutions (2+ years) that are very out of date and seem to keep saying that accessing the currently running testcase/spec is something they (protractor) try to keep hidden. I feel like wanting to do particular setup for a particular test case in a suite of test cases is not a unique thing. I could just be using the wrong search terms.
I am not sure how to do what you want with beforeEach(). But, I think you can get the same effect by using a helper file. This will allow you to setup a common file that any spec can reference so you can use a common set of functions. To set this up, you will:
Create a central file (I call mine util.js)
const helper = function(){
this.exampleFunction = function(num){
return num; //insert function here
}
this.exampleFunction2 = function(elem){
elem.click() //insert function here
}
}
Inside your spec.js file you will do:
const help = require('path/to/util.js');
const util = new help();
describe('Example with util',function(){
it('Should use util to click an element',function(){
let elem = $('div.yourItem');
util.exampleFunction2(elem);
});
});
You can then call these functions from any spec file. You would then be able to seperate your tests into seperate spec files, but have a common set of functions for the parts that are the same.
Another way to do this, without creating separate files is to just use a local function.
Example spec.js file:
describe('Should use functions',function(){
afterEach(function(){
$('button.logout').click();
)};
it('Should run test as user 1',function(){
$('#Username').sendKeys('User1');
$('#Password').sendKeys('Password1');
$('button.login).click();
doStuff();
)};
it('Should run test as user 2',function(){
$('#Username').sendKeys('User2');
$('#Password').sendKeys('Password2');
$('button.login').click();
doStuff();
)};
function doStuff(){
$('div.thing1').click();
$('div.thing2').click();
)};
)};
As per comments for multiple describes:
describe('Test with user 1',function(){
beforeEach(function(){
//login as user 1
});
it('Should do a thing',function(){
//does the thing as user 1
});
});
describe('Test with user 2',function(){
beforeEach(function(){
//login as user 2
});
it('Should do another thing',function(){
//does the other thing as user 2
});
});
The whole point of beforeEach is that it is the same for each test.
If you want to do different things, then they belong in the specific test.
Write a helper function and call it from the specific test if you want to have common functionality that does slightly different things depending on an argument.

Disable Jasmine expectation, like xdescribe or xit?

On the Jasmine website I see that we can disable suites by xdescribe or individual specs by xit. Is there a way to disable only an expectation (like xexpect)?
The reason why I'm asking this is because I'm writing e2e tests with Protractor and in our continuous integration we don't yet (if ever) have access to the database, though locally we can run real end to end tests with access to the database, for example.
I would like to mark individual expectations as optional, depending on a configuration or environment variable. It would be nice to make a switch once, and then create a wrapper around expect, that only fails if we are running the tests locally (with access to the database).
So for example I can create a new spec family:
if (process.env.DB_AVAILABLE) {
dbit = it;
} else {
dbit = xit;
}
and write specs that depend on database connection as following:
dbit('creates new user', function () {});
Is there a way to do the same with expect (e.g. dbexpect)?
If there is something fundamentally wrong with my approach, don't hold it back and let me know.
You could create your own xexpect by implementing all the methods/properties with an empty function:
var xexpect = function() {
return xexpect;
};
Object.getOwnPropertyNames(jasmine.Expectation.prototype).forEach(function(name){
xexpect[name] = xexpect;
});
Object.defineProperty(xexpect, 'not', {get: xexpect});
Usage :
xexpect(1).toBeGreaterThan(2);
xexpect(true).not.toEqual(true);

How to properly test an AngularJS Controller Function

We just started implementing jasmine tests in our AngularJS project and I have a question:
We want to test this controller function:
$scope.deleteClick = function () {
$scope.processing = true;
peopleNotesSrv.deleteNote($scope.currentOperator.operatorId, $scope.noteId, $scope.deleteSuccessCallback, $scope.deleteErrorCallback);
};
We wrote this following test:
it('deleteClick should pass proper parameters to peopleNoteSrv', function () {
$controllerConstructor('PeopleNoteEditCtrl', { $scope: $scope });
$scope.noteId = 5;
expect(function () { $scope.deleteClick(); }).not.toThrow();
});
This test makes sure that when we call the $scope.deleteClick() function that $scope.processing is set to true and that the call to peopleNotesSrv doesn't throw any errors because of invalid arguments. We are testing the two callback functions in separate tests.
Should we be testing that the peopleNotesSrv.deleteNote function was called so the test is more explicit? The way this test is written right now it doesn't really tell someone what the deleteClick() function does under the hood and that seems to be incorrect.
Ask yourself what you'd do if you had developed it using TDD. It pretty much goes the direction Sam pointed out, but here are some examples:
Controller Tests
start writing a test which would expect a deleteClick to exist.
Expect deleteClick to setup the loading state (check for processing = true)
Test whether a service is injected into the controller (peopleNotesSrv)
Check whether deleteClick calls the service (as already mentioned via spies)
Verify that $scope.noteId and the other $scope.params are present and set
This is as far as it relates to the Controller. All the criteria whether it fails or throws errors etc. should be tested in a Service.spec. Since I don't know your service in detail here some examples
Service Tests
Ensure deleteNote exists
Check what happens if wrong number of arguments (less or more) are supplied
Make some positive tests (like your noteId = 5)
Make some negative tests
Ensure callbacks are properly called
... and so on.
Testing for validity in controllers doesn't make a lot of sense because than you'd need to do it for every Controller you have out there. By isolating the Service as a separate Unit of Test and ensure that it fulfills all the requirements you can just use it without testing. It's kinda the same as you never would test jQuery features or in case of Angular jQLite, since you simply expect them to do what they should :)
EDIT:
Make controller tests fail on service call
Pretty easy lets take this example. First we create our Service Test to ensure that the call fails if not the proper number of arguments is supplied:
describe('Service: peopleNoteSrv', function () {
// load the service's module
beforeEach(module('angularControllerServicecallApp'));
// instantiate service
var peopleNoteSrv;
beforeEach(inject(function (_peopleNoteSrv_) {
peopleNoteSrv = _peopleNoteSrv_;
}));
it('should throw error on false number of arguments', function () {
expect(function() { peopleNoteSrv.deleteNote('justOneParameter'); }).toThrow();
});
});
Now to ensure that the test passes lets create the error throwing part in our service method
angular.module('angularControllerServicecallApp')
.service('peopleNoteSrv', function peopleNoteSrv() {
this.deleteNote = function(param1, param2, param3) {
if(arguments.length !== 3)
throw Error('Invalid number of arguments supplied');
return "OK";
};
});
Now lets create 2 demo controllers, FirstCtrl will do it properly, but SecondCtrl should fail
angular.module('angularControllerServicecallApp')
.controller('FirstCtrl', function ($scope, peopleNoteSrv) {
$scope.doIt = function() {
return peopleNoteSrv.deleteNote('param1', 'param2', 'param3');
}
});
angular.module('angularControllerServicecallApp')
.controller('SecondCtrl', function ($scope, peopleNoteSrv) {
$scope.doIt = function() {
return peopleNoteSrv.deleteNote('onlyOneParameter');
}
});
And both controller as a demo have following test:
it('should call Service properly', function () {
expect(scope.doIt()).toBe("OK");
});
Karma now spits out something like this:
Error: Invalid number of arguments supplied
at [PATH]/app/scripts/services/peoplenotesrv.js:15
at [PATH]/app/scripts/controllers/second.js:13
at [PATH]/test/spec/controllers/second.js:20
Thus you exactly know that you missed to update SecondCtrl. Of course this should work for any of your tests consuming the Service method.
Hope that's what you meant.
I think the answer is that it depends.
There are two cases:
1 - You also have a suite of tests for the peopleNotesSrv service.
In this case I would leave this test as-is or check a few more things around the specific functionality of $scope.deleteClick(), such as if there are any watchers on $scope.processing that do anything specific regarding a .deleteClick() call.
2 - You do not have any tests for all the possible functionality for the peopleNotesSrv service.
In this case I would write a more explicit test that does check that the .deleteNote() actually performed it's job.
In my opinion you should really build tests up and try to not test the same thing in more than one place, as this adds extra work and could produce holes in the tests if you think, "Well I can just test this specific case when it gets called from a specific function that calls it."
What if you ever want to reuse that deletNote() as part of a bigger function in a different place?Then you need to write another test for the same code because it is being called from a different function.
So I would aim for case 1, this way you can write all your tests for that service and then trust that those tests cover the rest of this particular test. If you throw errors on bad input or for failures to actually delete a note, you should trust that other code to test what it was designed to test. This will greatly speed up your test-writing time and increase the chance that your tests cover all the cases. It also keeps all the tests for that service in the same place in your test code.
I think also a good question to start with is what kind of test is this? Unit Test or End-to-End test?
I was assuming it was a Unit Test for my answer, if it was an End-to-End test, then you might want to keep following the function calls to verify everything is happening as you expect.
Here are some links on Unit Tests, End-to-End tests, and a pretty good article about both and Angular.
What's the difference between unit, functional, acceptance, and integration tests? (End-to-End tests can also be called Integration test)
http://www.sitepoint.com/unit-and-e2e-testing-in-angularjs/

Categories

Resources