I'd like to test that my app routes have meaning in the connected controller. Testing Marionette Modules in isolation with Jasmine Looked like it might work for me, but it didn't. (the spy isn't called, instead the controller function itself is called)
this is what I've got so far
Test:
it('navigates to showItem', function (){
var instance = new PmsRouter();
spyOn(instance.controller, "showItem");
Backbone.history.start();
instance.navigate("system/pms/1", {trigger:true});
expect(instance.controller.showItem).toHaveBeenCalledWith("1");
window.location.hash = ""
});
Modules:
'use strict';
define([
'underscore',
'modules/admin/system/pms/PmsController',
'marionette'
], function(_ ,PmsController) {
var PmsRouter = Marionette.AppRouter.extend({
controller: new PmsController(),
appRoutes: {
'system/pms/:pms_id': 'showItem'
}
});
return PmsRouter;
});
Any help is appreciated!
Related
I have a problem with injection dependency service in my test. I tried inject in different ways, but problem still stays.
Injection works only in one way, via
var $injector = angular.injector(['app']);
$auth = $injector.get('$auth');
but i think it's not good idea.
My config files:
files: [
'../www/lib/angular/angular.js',
'../www/lib/moment/min/moment.min.js',
'../www/lib/moment/locale/ru.js',
'../www/lib/highcharts/adapters/standalone-framework.js',
'../www/lib/highcharts/highcharts.js',
'../www/lib/highcharts/highcharts-more.js',
'../www/lib/highcharts/modules/funnel.js',
'../www/lib/nouislider/distribute/nouislider.js',
'../www/lib/angular-cache/dist/angular-cache.js',
'../www/lib/angular-moment/angular-moment.js',
'../www/lib/ngstorage/ngStorage.min.js',
'../www/lib/nouislider-angular/nouislider.js',
'../www/lib/highcharts-ng/dist/highcharts-ng.js',
'../www/lib/angular-lz-string/angular-lz-string.js',
'../www/lib/ng-lodash/build/ng-lodash.min.js',
'../www/lib/angular-mocks/angular-mocks.js',
'../www/js/app.js',
'unit/*.js'
],
My test:
describe('$auth', function () {
var auth;
beforeEach(module('app'));
beforeEach(inject(function($auth) {
auth = $auth;
console.log(auth); // Not executes
}));
it('should call requestAuth', function () {
expect(auth).not.toBeUndefined(); // false here
});
});
You probalby need
beforeEach(module('app', 'ng'));
or whatever module $auth is in
I'm trying to run a unit test on a function (testFunc). testFunc calls another function (secondFunc) which I would like to mock. Can I mock secondFunc so that when it is called in the context of testFunc, the spiedOn version of secondFunc is called? If not, how should I reformat my browserify module to make it testable?
Currently the setup looks something like this:
app.js (Browserify Module)
module.exports = (function () {
function testFunc() {
secondFunc();
}
function secondFunc(){
console.log('not mocked!');
}
return {
testFunc, secondFunc
};
})();
test.js (Jasmine Test)
describe("testFunc", () => {
let app = require('./app');
beforeEach(() => {
spyOn(app, 'secondFunc');
});
it("should call secondFunc spy", () => {
app.testFunc();
expect(app.secondFunc).toHaveBeenCalled();
});
});
The way you have it now, the spyOn is replacing the secondFunc property on your returned object with a proxy, but your code calls the secondFunc function that is inside the closure of the anonymous function. There are several ways to restructure your code to better expose the functions.
You could structure your module this way:
exports.testFunc = function() {
exports.secondFunc();
}
exports.secondFunc = function(){
console.log('not mocked!');
}
which is a lot smaller, easier to read, and let you mock the secondFunc function.
The reason this is happening is because you are setting up a mock on the returned object, but the code is calling the internal function. What I've done in the past is something like this:
module.exports = (function () {
function testFunc() {
api.secondFunc(); // Call the API function, which is what is mocked
}
function secondFunc(){
console.log('not mocked!');
}
var api = {
testFunc, secondFunc
};
return api;
})();
Here are 2 samples of the same test. The only difference is that first one uses a promise in beforeAll block to assign a value to the variable while the second one assigns the value directly.
I raised a similar question Running spec after promise has been resolved with one of the comments pointing to this issue https://github.com/jasmine/jasmine/issues/412 which says that this is not supported in Jasmine. Has somebody figured out any workaround?
This fails with TypeError: Cannot read property 'forEach' of undefined
describe('Async car test', function () {
var cars;
beforeAll(function (done) {
// getCars() is a promise which resolves to ['audi', 'bmw']
getCars().then(function (data) {
cars = data;
console.log(cars) // ['audi', 'bmw']
done();
});
});
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
This works fine
describe('Car test', function () {
var cars = ['audi', 'bmw'];
cars.forEach(function (car) {
it('car ' + car, function () {
expect(car).toBe(car);
});
});
});
Posting it as an answer, because I can't see things properly in comments.
I'm actually generating tests in my spec as well, and I'm using https://www.npmjs.com/package/jasmine-data-provider , I think you probably cannot generate it directly from resolved promise. And wrapping in another it doesn't work for you. This should work:
var using = require('jasmine-data-provider');
using(cars.forEach, function (car) {
it(car + ' should be' + car, function () {
expect(car).toBe(car);
});
});
This is not an issue with jasmine, it is an issue with your code.
beforeAll does not block subsequent code below the statement. it blocks code that is defined in it('should ...', (done)=>{...});
it('should have cars', (done) => {
cars.forEach(function (car) {
expect(car).toBe(car);
});
});
Since Jasmine does not support adding tests at runtime, the trick is to request the asynchronous data before starting Jasmine, and then using the retrieved data during runtime instead. This can be achieved with a singleton and programmatically starting Jasmine.
See here for a working example.
// car-collection.js
class CarCollection {
static load() {
return this.request()
then((data) => this.cars = data);
}
static request() {
// in practice this function would do something cooler
return Promise.resolve(['audi', 'bmw']);
}
}
modules.export = CarCollection;
Since CarCollection has methods that are static they will be shared across imports and this.cars will persist.
// launcher.js
const Jasmine = require('jasmine');
const CarCollection = require('./car-collection');
CarCollection.load()
.then(() => {
console.log(`car count is ${CarCollection.cars.length}`); // prints: car count is 2
const jasmine = new Jasmine();
jasmine.loadConfigFile(...); // path to jasmine.json
jasmine.execute();
});
An important step here is configure jasmine to know where to look for the test files. Either by loading a config or passing specifics into the execute function.
// car.spec.js
const CarCollection = require('./car-collection');
describe('test', function () {
CarCollection.cars.forEach((car) => {
it('test' + car, () => {
expect(car).toBe(car);
});
});
});
Now run node ./launcher.js and the tests should run.
I'm trying to send data to my view from an AJAX call to my API. I am able to successfully hit my API and get data, but I was having problems with the view rendering before the AJAX call came back.
I'm trying to wrap my AJAX call in a Promise but it's not working. Here's my layout
Controller
.controller('DashCtrl', function($scope, Tweets) {
$scope.tweets = Tweets.all()
})
Factory doing ajax call
.factory('Tweets', function($http) {
$http.get('http://localhost:3000/tweets')
.success(function(data) {
var tweets = data
debugger
})
return {
all: function() {
//should return the results of the AJAX call when it's complete
}
}
});
I've tried making wrapping the ajax call into a function and using .then(function(payload){ return payload.data }) - Payload.data has my data but its never returned when I call the function. I'm new to angular, so I would appreciate any help or insight.
You should define your factory as
.factory('Tweets', function($http) {
return {
all: function() {
return $http.get('http://localhost:3000/tweets')
.then(function(response) {
return reponse.data;
})
}
}
});
Then change your controller to
.controller('DashCtrl', function($scope, Tweets) {
Tweets.all().then(function(data) {
$scope.tweets = data;
});
})
Use the $resource service. The docs don't mention it, but comments in the source do.
$resolved: true after first server interaction is completed (either with success or rejection), false before that.
So in the controller:
$scope.tweets = $resource('/tweets').query()
And in the view:
<div ng-if="tweets.$resolved">
Loading data with ngResource or from factory promise callback are viable options, but there's one more way nobody mentioned yet: resolve data to controller via route definition. This approach allows to write simplistic controllers that don't know how to load data at all. In most cases it will be more than enough if you don't need to load data dynamically, like pagination or infinite scroll.
You will need to define route and resolve function:
angular
.module('app', ['ngRoute'])
.config(function ($routeProvider) {
$routeProvider
.when('/', {
controller: 'ctrl',
controllerAs: 'view',
templateUrl: 'view.html',
resolve: {
tweets: function (Tweets) {
return Tweets.all();
}
}
})
})
The tweets property on resolve will inject loaded data into controller as tweets, all you have to do is just assign received data:
.controller('ctrl', function (tweets) {
this.tweets = tweets;
});
In addition, here's how Tweets service might look like:
.factory('Tweets', function ($timeout) {
function all () {
return $timeout(function () {
return ["hey", "there"];
});
}
return {
all: all
};
})
Basically, it exposes methods that return promise, returning some data ($timeout returns promise too, so I've used it instead of $http for example purpose).
Full example on JS Bin.
describe('my homepage', function() {
var ptor = protractor.getInstance();
beforeEach(function(){
// ptor.ignoreSynchronization = true;
ptor.get('http://localhost/myApp/home.html');
// ptor.sleep(5000);
})
describe('login', function(){
var email = element.all(protractor.By.id('email'))
, pass = ptor.findElement(protractor.By.id('password'))
, loginBtn = ptor.findElement(protractor.By.css('#login button'))
;
it('should input and login', function(){
// email.then(function(obj){
// console.log('email', obj)
// })
email.sendKeys('josephine#hotmail.com');
pass.sendKeys('shakalakabam');
loginBtn.click();
})
})
});
the above code returns
Error: Error while waiting for Protractor to sync with the page: {}
and I have no idea why this is, ptor load the page correctly, it seem to be the selection of the elements that fails.
TO SSHMSH:
Thanks, your almost right, and gave me the right philosophy, so the key is to ptor.sleep(3000) to have each page wait til ptor is in sync with the project.
I got the same error message (Angular 1.2.13). My tests were kicked off too early and Protractor didn't seem to wait for Angular to load.
It appeared that I had misconfigured the protractor config file. When the ng-app directive is not defined on the BODY-element, but on a descendant, you have to adjust the rootElement property in your protractor config file to the selector that defines your angular root element, for example:
// protractor-conf.js
rootElement: '.my-app',
when your HTML is:
<div ng-app="myApp" class="my-app">
I'm using ChromeDriver and the above error usually occurs for the first test. I've managed to get around it like this:
ptor.ignoreSynchronization = true;
ptor.get(targetUrl);
ptor.wait(
function() {
return ptor.driver.getCurrentUrl().then(
function(url) {
return targetUrl == url;
});
}, 2000, 'It\'s taking too long to load ' + targetUrl + '!'
);
Essentially you are waiting for the current URL of the browser to become what you've asked for and allow 2s for this to happen.
You probably want to switch the ignoreSynchronization = false afterwards, possibly wrapping it in a ptor.wait(...). Just wondering, would uncommenting the ptor.sleep(5000); not help?
EDIT:
After some experience with Promise/Deferred I've realised the correct way of doing this would be:
loginBtn.click().then(function () {
ptor.getCurrentUrl(targetUrl).then(function (newURL){
expect(newURL).toBe(whatItShouldBe);
});
});
Please note that if you are changing the URL (that is, moving away from the current AngularJS activated page to another, implying the AngularJS library needs to reload and init) than, at least in my experience, there's no way of avoiding the ptor.sleep(...) call. The above will only work if you are staying on the same Angular page, but changing the part of URL after the hashtag.
In my case, I encountered the error with the following code:
describe("application", function() {
it("should set the title", function() {
browser.getTitle().then(function(title) {
expect(title).toEqual("Welcome");
});
});
});
Fixed it by doing this:
describe("application", function() {
it("should set the title", function() {
browser.get("#/home").then(function() {
return browser.getTitle();
}).then(function(title) {
expect(title).toEqual("Welcome");
});
});
});
In other words, I was forgetting to navigate to the page I wanted to test, so Protractor was having trouble finding Angular. D'oh!
The rootElement param of the exports.config object defined in your protractor configuration file must match the element containing your ng-app directive. This doesn't have to be uniquely identifying the element -- 'div' suffices if the directive is in a div, as in my case.
From referenceConf.js:
// Selector for the element housing the angular app - this defaults to
// body, but is necessary if ng-app is on a descendant of <body>
rootElement: 'div',
I got started with Protractor by watching the otherwise excellent egghead.io lecture, where he uses a condensed exports.config. Since rootElement defaults to body, there is no hint as to what is wrong with your configuration if you don't start with a copy of the provided reference configuration, and even then the
Error while waiting for Protractor to sync with the page: {}
message doesn't give much of a clue.
I had to switch from doing this:
describe('navigation', function(){
browser.get('');
var navbar = element(by.css('#nav'));
it('should have a link to home in the navbar', function(){
//validate
});
it('should have a link to search in the navbar', function(){
//validate
});
});
to doing this:
describe('navigation', function(){
beforeEach(function(){
browser.get('');
});
var navbar = element(by.css('#nav'));
it('should have a link to home in the navbar', function(){
//validate
});
it('should have a link to search in the navbar', function(){
//validate
});
});
the key diff being:
beforeEach(function(){
browser.get('');
});
hope this may help someone.
I was getting this error:
Failed: Error while waiting for Protractor to sync with the page: "window.angular is undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details"
The solution was to call page.navigateTo() before page.getTitle().
Before:
import { AppPage } from './app.po';
describe('App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should have the correct title', () => {
expect(page.getTitle()).toEqual('...');
})
});
After:
import { AppPage } from './app.po';
describe('App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
page.navigateTo();
});
it('should have the correct title', () => {
expect(page.getTitle()).toEqual('...');
})
});
If you are using
browser.restart()
in your spec some times, it throws the same error.
Try to use
await browser.restart()