I'm trying to TDD a purely front-end project based on a single HTML page using Jasmine and Karma to unit test.
The document of Jasmine tells nothing about DOM manipulations, which is one of the most important things I'm trying to test.
The question is simple, how do I examine the DOM changes and the content changes of a HTML page?
For example, say if I have code like below:
HTML file
Say Hello
JS file:
// Router
App.Router = Backbone.Router.extend({
routes: {
'': 'home',
'say_hello': 'sayHello'
}
});
var router = new App.Router();
router.on('route:sayHello', function(){
$("body").append("Hello World");
});
Backbone.history.start();
How do I test such behavior:
If someone clicks "Say Hello" link, the text in the html body will contain "Hello World"
And even more, how do I check the CSS changes or simulate HTML events (mouseover, focus, change, load, etc...)? Is that possible with Jasmine and Karma?
Thanks
TDD is to unit testing - unit - small methods with mocks, stubs and spies. Manipulation on the DOM should be done by methods and you want to tests those methods.
Instead of testing jQuery engine selector and method 'append' call some method. In the test create spy that will be expecting that your methods was called.
To check DOM manipulation you can use Jasmine jQuery matcher -https://github.com/velesin/jasmine-jquery
If you need to test small html snippets you should add them programatically to your test.
var fixture = '<h1>My HTML fragment</h1>';
document.body.insertAdjacentHTML(
'afterbegin',
fixture);
You can also manage to link stylesheets
var fixture = '<link rel="stylesheet" media="all" \
href="https://www.npmjs.com/static/css/index.css?last-changed=10d466883fa405391313dc8294e783fd">\
<header><div class="header-item header-nav-menu-container">\
<h1 style="color:#FFF">My HTML fragment</h1></div></header>';
document.body.insertAdjacentHTML(
'afterbegin',
fixture);
Also to serve the stylesheets with karma you should add the to the list of files and link them like this
Related
There other question on SO with same problem, but the solutions didnt worked for me.
Here my spec.js
describe('Protractor Demo App', function() {
it('should have a title', function() {
browser.driver.get('http://rent-front-static.s3-website-us-east-1.amazonaws.com/');
expect(browser.getTitle()).toEqual('How It Works');
});
});
And here my conf.js
exports.config = {
framework: 'jasmine',
rootElement: 'body',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['spec.js']
}
So when i try to run my test im getting the error
Message:
Failed: Error while waiting for Protractor to sync with the page: "[ng:test] no injector found for element argument to getTestability\nhttp://errors.angularjs.org/1.5.0/ng/test"
Stack:
Error: Failed: Error while waiting for Protractor to sync with the page: "[ng:test] no injector found for element argument to getTestability\nhttp://errors.angularjs.org/1.5.0/ng/test"
at C:\Users\ShapeR\PycharmProjects\ratest\node_modules\jasminewd2\index.js:101:16
at Promise.invokeCallback_ (C:\Users\ShapeR\PycharmProjects\ratest\node_modules\selenium-webdriver\lib\promise.js:1329:14)
at TaskQueue.execute_ (C:\Users\ShapeR\PycharmProjects\ratest\node_modules\selenium-webdriver\lib\promise.js:2790:14)
at TaskQueue.executeNext_ (C:\Users\ShapeR\PycharmProjects\ratest\node_modules\selenium-webdriver\lib\promise.js:2773:21)
1 spec, 1 failure
I have a manual bootstrapping for body element and set the rootElement to body in config, but it didnt help. I even tried to remove manual boostraping and just add ng-app='rentapplicationApp' to body element, but it changes nothing, still same error.
So what is wrong?
The core value of Protractor is that it manages the angular loading for you, including sync, so you are very right in not wanting to use: browser.ignoreSynchronization = true.
The error message you are getting is that protractor is unable to locate angular in order to sync. This is because of a combination of two issues:
The page isn't ready, angular isn't loaded
It is unable to locate the ng-app, even once the page is loaded
Firstly, from Protractor setup page.
If your page does manual bootstrap Protractor will not be able to load your page using browser.get. Instead, use the base webdriver instance - browser.driver.get. This means that Protractor does not know when your page is fully loaded, and you may need to add a wait statement to make sure your tests avoid race conditions.
Solution 1
Add a wait statement.
Solution 2
If you don't have a good reason for manually bootstrapping, or don't want to wait:
Stop manually bootstrapping the app
Use browser.get over browser.driver.get
[ng:test] no injector found for element argument to getTestability
I suspect there is something wrong with the application itself, the way it is bootstrapped, since Protractor actually finds the root element (you can explicitly set the rootElement: "body.root" inside your config as well), but fails to setup the injector for the root element.
I'd try to figure out what is happening it step by step - first, try running the protractor test against a non-built application started directly from the source to ensure that this is not the webpack's or other part's of the build fault.
Then, I'd try upgrading to the latest 1.x Angular and Protractor (3.3.0 is the most recent version).
The most simple workaround at the moment would be to turn the sync between Protractor and Angular off, by using browser.ignoreSynchronization = true:
describe("Strange Protractor/Angular problem", function () {
beforeEach(function () {
browser.ignoreSynchronization = true;
browser.get("https://dl.dropboxusercontent.com/u/597502/vxcv/index.html");
var elm = $(".navbar-brand");
browser.wait(EC.presenceOf(elm), 5000);
});
it("should have an expected title", function () {
expect($(".navbar-brand").getText()).toEqual('RENT APPLICATION');
});
});
Of course, there are downsides to this approach - you would have to use browser.wait calls here and there to tackle the timing issue. The test flow would not be as natural and simple as it would be when sync is on.
The problem was in bootstrapping my app. For some reason it doesnt work with ng-app tag. The only working solution was to manual bootstrap it
angular.bootstrap(document, ["rentapplicationApp"]);
And first argument should be dom node, not a string, like it was in my case, although with string the app will work, but getTestability will fail.
I am trying to add jQuery selector controls to a phantomJS headless browser for testing purposes.
Is it possible?
To this point we have been overwriting the check to see if the control exists and haven't made any attempts to manipulate values and test onChange events.
The test I am wanting to run is this:
//arrange
//OnChange results in values being stored in cookies
$('#TestControl').val(cookieValue).trigger('change');
//act
var result = $.cookie(cookieName);
//assert
expect(result).toBe(cookieValue);
The problem is that I don't know how to actually create $('#TestControl')
For creating DOM elements in phantomJS/jasmine, you could do the following:
var TestControl = $('<input id="TestControl"/>');
$(document.body).append(TestControl);
However, it is better to define the markup in html. The fixture module of jasmine-jquery allows you to load HTML content to be used by your tests. The overall workflow is as follows:
In myfixture.html file:
<div id="my-fixture"><input id="TestControl"/></div>
Inside your test:
loadFixtures('myfixture.html')
$('#TestControl').val(cookieValue).trigger('change')
expect($('#my-fixture')).to...
I was just going through the angular.js documentation HERE and saw the section called:
Multi-slot transclusion
and the following code in the protractor.js tab:
it('should have transcluded the title and the body', function() {
var titleElement = element(by.model('title'));
titleElement.clear();
titleElement.sendKeys('TITLE');
var textElement = element(by.model('text'));
textElement.clear();
textElement.sendKeys('TEXT');
expect(element(by.binding('title')).getText()).toEqual('TITLE');
expect(element(by.binding('text')).getText()).toEqual('TEXT');
});
I was just wondering if thats angular's version of jQuery
From E2E section of angular.js site:
Protractor is a Node.js program, and runs end-to-end tests that are also written in JavaScript and run with node. Protractor uses WebDriver to control browsers and simulate user actions.
I have some feature tests that run with a fixture (loaded with jasmine-jquery) that has some Knockout bindings in the HTML. At the begin of each test I want to start with a viewModel in its initial state.
If I call applyBindings() in the beforeEach() with a new instance of the viewModel I get this error from Knockout
Error: You cannot apply bindings multiple times to the same element.
If I try to revert the properties of the existing viewModel to match its initial state I still get an error. I believe this is because the fixture's HTML is removed after each test - this probably breaks the bindings?
I've also tried a suggestion that came up when Googling which was to use the cleanNode function in Knockout. This isn't part of the API (it only designed to be used by Knockout internally) and no matter what I tried it didn't resolve the issue.
It feels like I'm taking the wrong approach to this. tl;dr; How does everybody else test Knockout with Jasmine?
Thanks for any help
I generally append an element in beforeEach, apply bindings to that element, and ko.removeNode on it in afterEach. Something like:
var fixture;
beforeEach(function() {
fixture = document.createElement("div");
document.body.appendChild(fixture);
});
afterEach(function() {
ko.removeNode(fixture);
});
Then use fixture as the second argument to any applyBindings calls like: ko.applyBindings(myTestViewModel, fixture);
The way I solved this was changing my js to check for jasmine e.g.:
if (!window.jasmine)
ko.applyBindings(viewModel);
I am fairly certain that the issue is that the jquery bindings set to run on $(document).ready do not have the fixture html available to them. So when my events occur that are intended to make a change to the DOM via a jquery function, nothing happens, and my tests fail. I saw a "solution" to this problem here, but that solution which did work for me, required changing my working jquery function to bind with the .live method instead of the .click method. I have two issue with this. First I do not want to have to change my working code so that the tests will pass properly. The testing framework ought to test whether the code will work in the app, where the DOM load and the javascript bindings occur in the correct order. The second issue I have with the solution is that .on and .delegate both did not work for some reason and the only thing that ended up working was to use the .live method which is currently in the process of being deprecated. In conclusion, I would like to figure out how to change either my tests or the testing framework itself, such that the fixtures are loaded before the functions that run in $(document).ready.
I am working with rails 3.2.8 and I have two different branches set up to experiment with jasmine testing. One of the branches uses the jasminerice gem and the other uses the jasmine-rails gem. The Javascript and jQuery(when using the .live method) tests all pass properly in both of these set-ups.
Here is a description of the branch using jasminerice:
Here are the lines from my Gemfile.lock file describing the jasmine and jQuery set-up:
jasminerice (0.0.9)
coffee-rails
haml
jquery-rails (2.1.3)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
Jasminerice includes the jasmine-jquery extension by default. The jasmine-jQuery extension provides the toHaveText method I am using in the tests.
The test file jasmine_jquery_test.js which is located directly within the spec/javascripts directory contains this content:
#= require application
describe ("my basic jasmine jquery test", function(){
beforeEach(function(){
$('<a id="test_link" href="somewhere.html">My test link</a>').appendTo('body');
});
afterEach(function(){
$('a#test_link').remove();
});
it ("does some basic jQuery thing", function () {
$('a#test_link').click();
expect($("a#test_link")).toHaveText('My test link is now longer');
});
it ("does some the same basic jQuery thing with a different trigger type", function () {
$('a#test_link').trigger('click');
expect($("a#test_link")).toHaveText('My test link is now longer');
});
});
describe ('subtraction', function(){
var a = 1;
var b = 2;
it("returns the correct answer", function(){
expect(subtraction(a,b)).toBe(-1);
});
});
My javascript file tests.js which is located in the app/assets/javascripts dir has this content:
function subtraction(a,b){
return a - b;
}
jQuery (function($) {
/*
The function I would rather use -
$("a#test_link").click(changeTheTextOfTheLink)
function changeTheTextOfTheLink(e) {
e.preventDefault()
$("a#test_link").append(' is now longer');
}
*/
$("a#test_link").live('click', function(e) {
e.preventDefault();
$("a#test_link").append(' is now longer');
});
});
My application.js file located in the same app/assets/javascripts dir has this content:
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require vendor
//= require_tree .
And the description of the jasmine-rails branch can be found in the content of my first jasmine test related stackoverflow question.
So if anyone has an idea how to manipulate the tests such that the $(document).ready functions are run after the fixtures are loaded I would very much like to hear your thoughts?
Adding the jQuery directly to the html fixture worked for me to get the desired behavior. It is not exactly an answer to this question, but I am starting to believe it's the best solution currently available. My changes where made in the jasmine-rails gem set-up and I haven't yet tried it in the jasminerice branch.
Here is my test file:
describe ("my basic jasmine jquery test", function(){
beforeEach(function(){
loadFixtures('myfixture.html');
});
it ("does some basic jQuery thing", function () {
$('a#test_link').click();
expect($("a#test_link")).toHaveText('My test link is now longer');
});
it ("does the same basic jQuery thing with a different event initiation method", function () {
$('a#test_link').trigger('click');
expect($("a#test_link")).toHaveText('My test link is now longer');
});
});
describe ('substraction', function(){
var a = 1;
var b = 2;
it("returns the correct answer", function(){
expect(substraction(a,b)).toBe(-1);
});
});
And here is my fixture file:
<a id="test_link" href="somewhere.html">My test link</a>
<script>
$("a#test_link").click(changeTheTextOfTheLink)
function changeTheTextOfTheLink(e) {
e.preventDefault()
$("a#test_link").append(' is now longer');
}
</script>
Personally, I consider this to be a bug jasmine-jquery. I "solved" this by making my fixtures part of the SpecRunner.html. Not ideal but it works in simple cases.
Have you tried jasmine-jquery?
https://github.com/velesin/jasmine-jquery
From it's github page,
jasmine-jquery provides two extensions for Jasmine JavaScript Testing Framework:
a set of custom matchers for jQuery framework
an API for handling HTML, CSS, and JSON fixtures in your specs
I used dom_munger to automatically generate the fixture for me, so adding the jquery code manually inside the fixture isn't an option for me. I've been searching for the same answer, and the best solution I can think of right now is to use event delegate in jQuery.
For example, instead of writing this:
$('#target').click(function(){
// Do work
});
Do this:
$(document).on('click', '#target', function(){
// Do work
});
In this way, the listener is attached to the document, which is present before the fixture is loaded. So when the click event happens on #target, it bubbles up to document. Document checks if the origin matches the second argument - '#target'. If it matches, the handler function will be called.
I know there are limitations to this, such as initializing plug-ins, but it does solve part of the problem.
I took a slightly different approach to those described here to solve this limitation.
Disclaimer: I work for Microsoft so I cannot post my actual source code here without legal approval, so instead I'll just describe an approach that works well.
Rather than trying to change how document.ready works, I modified the jasmine-jquery source to support a function called fixturesReady that takes a callback.
Essentially this is defined inside an IIFE, and holds an array of callbacks per event name privately, that are iterated over on trigger.
Then on this chunk you can put the trigger:
jasmine.Fixtures.prototype.load = function() {
this.cleanUp()
this.createContainer_(this.read.apply(this, arguments))
fixturesReady.trigger("loaded", arguments); // <----- ADD THIS
}
So in your tests now you can code against:
fixturesReady(yourCallback)
And you know that when yourCallback is invoked you can safely query the DOM for your fixture elements.
Additionally, if you're using the async.beforeEach jasmine plugin you could do this:
async.beforeEach(function(done){
fixturesReady(done);
...
loadFixtures("....");
});
Hope that helps, and serves to give you some ideas about solving this problem.
NOTE: Remember in your plugin to clear any callbacks in an afterEach block if necessary :)
It has been a while since this question was asked (and an answer accepted).
Here is a better approach to structure your test and load the script for testing with jasmine.
templates/link.tmpl.html
<a id="test_link" href="somewhere.html">My test link</a>
js/test-link.js
$("a#test_link").click(changeTheTextOfTheLink)
function changeTheTextOfTheLink(e) {
e.preventDefault()
$("a#test_link").append(' is now longer');
}
specs/link-test.spec.js
describe ("my basic jasmine jquery test", function(){
jasmine.getFixtures().fixturesPath = ''
var loadScript = function(path) {
appendSetFixtures('<script src="' + path + '"></script>');
};
beforeEach(function(){
loadFixtures('templates/link.tmpl.html');
loadScript('js/test-link.js');
});
it ("does some basic jQuery thing", function () {
$('a#test_link').click();
expect($("a#test_link")).toHaveText('My test link is now longer');
});
it ("does the same basic jQuery thing with a different event initiation method", function () {
$('a#test_link').trigger('click');
expect($("a#test_link")).toHaveText('My test link is now longer');
});
});
Spec Runner
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Spec Runner</title>
<link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.3/jasmine.css">
<script type="text/javascript" src="lib/jasmine-2.0.3/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-2.0.3/jasmine-html.js"></script>
<script src="lib/jasmine-2.2/boot.js"></script>
<script type="text/javascript" src="lib/jquery-2.1.3.min.js"></script>
<script type="text/javascript" src="lib/jasmine-jquery.js"></script>
<script type="text/javascript" src="specs/link-test.spec.js"></script>
</head>
<body>
</body>
</html>