Jasmine: Testing promises. Is there something like $provide.value() for Angular2? - angularjs

I'm trying to test a promise in Angular2, something like this:
this.fooService.getData().then(data => this.fooData = data);
This article shows how to test promises for Angular 1.
I know I can do this:
beforeEachProviders(() => [
provide(FooService, {useClass: fooService})
]);
But I'm hoping $provide.value is some other functionality I can use because I keep getting this error 'undefined' is not an object (evaluating 'this.fooService.getData().then')
It doesn't like the then.

You can use fakeAsync and flushMicrotasks or tick to pass the time in angular/core/testing.
One of my test might look something like
it('should set the active module', <any>fakeAsync(() => {
let expected = { id: 'id1' };
jasmine.createSpyObj<FooService>('FooService'['getData']);
spyService.fooService.and.callFake(() => {
return Promise.resolve(expected);
});
let component = new FooComponent(fooService);
component.getData();
flushMicrotasks();
expect(component.fooData).toEqual(expected);
}));

Related

Jest testing a class in window

I'm super lost how to do this.
We've got a class in window for Loqate called window.pca.Addresses
Then in our code we run this snippet of code in a react method:
const shippingControl = new pca.Address(shippingFields, { key: process.env.LOQATE_API_KEY });
window.setTimeout(() => {
shippingControl.load();
}, 3000);
My question is how do I write a jest test to mock the Address class in the window and then also add the load() method so we can test if it has been fired or not.
I've tried to spyOn the window object as both global and window as well as mocking it with jest.mock() which also hasn't worked.
I'm wondering if anyone else can guide me on how to do this?
Many thanks
You can add beforeEach to your test something like that:
beforeEach(() => {
Object.defineProperty(window, "pca", {
value: {
Addresses: { load: jest.fn(() => null) }
},
writable: true
});
});
You can test if it has been fired like this:
expect(window.pca.Addresses.load).toHaveBeenCalledTimes(1);

Angular 1.5 component unit testing with webpack

I'm trying to test a component with controller, with some bindings:
class AppSpecificController {
constructor() {
this.text = this.initialText ? this.initialText : 'No initial text has been specified.';
}
}
export const AppSpecificComponent = {
bindings: {
'initialText': '<bindInitialText'
},
templateUrl: '/app/components/app-specific/app-specific.html',
controller: AppSpecificController,
controllerAs: 'appSpecific'
};
export default AppSpecificComponent;
In my unit test file I don't want to load the complete application, just the things I need. So I figured to mock a module or just create a new one named something with mock, add the component to that module, and load that module:
import {AppSpecificComponent} from './app-specific.component';
describe('AppSpecificComponent', () => {
let controller;
let scope;
let $componentController;
beforeEach(() => {
angular.module('mock-module', [])
.component('appSpecific', AppSpecificComponent);
// this fails
module('mock-module');
inject((_$componentController_, $rootScope) => {
scope = $rootScope.$new();
$componentController = _$componentController_;
});
controller = $componentController('appSpecific', {$scope: scope}, {initialText: 'Some text'});
});
it('should test something', () => {
expect(true).toBeTruthy();
});
});
Creating the module mock-module is fine, but loading it fails, and obviously injecting stuff in the not-so-much-loaded-module fails, as well as creating a controller on which I can start testing. It would be nice to be able to test the components individually, separate from the application in which it runs.
Just using the new operator for the class AppSpecificController doesn't work since then the bindings you receive from the component are not there:
// fails, no bindings available in controller
controller = new AppSpecificController();
I found the answer somewhere else on StackOverflow, not sure where anymore. The answer however I was looking for:
angular.mock.module($provide => {
$provide.controller('SomeController', () => { ... });
$provide.constant('someConstant', 'some constant');
});

How can I use _lodash debounce with AngularJS?

It was suggested I could use the following:
$wmdInput.on('keyup', _.debounce(function () {
var rawContent = $wmdInput.val();
scope.$apply(function () {
ngModel.$setViewValue(rawContent);
});
}), 300);
However this gives a message: Uncaught TypeError: Object 500 has no method 'apply' from jQuery.
Does anyone know how I could fix this?
As an FYI it was also suggested I could use the following:
var promise;
$wmdInput.on('keyup', function () {
$timeout.cancel(promise);
promise = $timeout(function() {
var rawContent = $wmdInput.val();
ngModel.$setViewValue(rawContent);
}, 2000);
});
I would appreciate comments from the AngularJS experts here. Would the second code work as well as using _lodash? I noticed a lot of posts on github so I hope to see something implemented in the core AngularJS soon.
The (now deleted) answer from #Satpal was correct: you are passing the timeout value (300) as an argument to $wmdInput.on, and not _.debounce.
So try this:
$wmdInput.on('keyup', _.debounce(function () {
var rawContent = $wmdInput.val();
scope.$apply(function () {
ngModel.$setViewValue(rawContent);
});
}, 300));

Testing backbone router with jasmine and sinon. Cannot call the pushState.

I was following this question to test the router. My router is really simple:
App.Router = Backbone.Router.extend({
routes:{
"": "index",
"help": "help"
},
help: function() {/* not really needed */ },
index: function(){
// does something
}
});
And this is an apptempted translation of what should be the test using jasmine with sinon:
it('triggers the "index" route', function() {
var router = new App.Router();
Backbone.history.start();
//Not calling navigate it's a problem
router.navigate('help', {
trigger : true, replace: true
});
var index = sinon.spy(router, 'index');
var spyHasPS = sinon.spy(function(
data, title, url) {
expect(url).toEqual('/');
router.index();
});
var spyNoPS = sinon.spy(function(loc, frag) {
expect(frag).toEqual('');
router.index();
});
if (Backbone.history._hasPushState) {
pushStateSpy = sinon.stub(window.history, 'pushState', spyHasPS );
// window.history.pushState();
} else if (Backbone.history._wantsHashChange) {
pushStateSpy = sinon.stub(Backbone.history, '_updateHash', spyNoPS);
//Backbone.history._updateHash(window.location, '');
}
router.navigate('', {
trigger : true, replace: true
});
expect(pushStateSpy.called).toBe(true);
expect(index.called).toBe(true);
});
This test works but I could achieve it because I navigated first on "help". "help" was just something I created to pass the test but the original question didn't do it and was passing. Did I do something wrong? I also run his test but the error I'm getting is:
Expected spy _updateHash to have been called. Error: Expected spy
_updateHash to have been called.
at null.<anonymous> (/src/test/js/spec/wfcRouter.spec.js:65:32) Expected spy index to have been called.
I believe the "problem" is in the navigate function. At a certain point in the navigate: function(fragment, options) we have this control:
fragment = this.getFragment(fragment || '');
if (this.fragment === fragment) return;
So...does it make sense to test the pushState when you just have one route (remember I added "help" just to make this test pass so I don't need it)? If it does make sense, how can I achieve this test?
It seems like what you are testing is Backbone code, but there's no need for you to test that: presumably the Backbone code has been tested plenty by Jeremy Ashkenas (and if you look at the Backbone project on GitHub you will see that he does in fact have a comprehensive test suite). So, rather than re-testing code you didn't write that's already been tested, what you really should be testing is the code you wrote.
If you agree with that principle, then you can simplify your test a great deal, down to just:
it('triggers the "index" route', function() {
var router = new App.Router();
router.index();
expect(thingThatShouldHaveHappenedInIndexRouteDidHappen).toBe(true);
});

Backbone Collection is empty when testing with Jasmine

I'm just getting started with Jasmine and trying to set up some tests for the first time. I have a Backbone collection. I figured I would get my collection as part of the beforeEach() method, then perform tests against it.
I have a test json object that I used while I prototyped my app, so rather than mocking an call, I'd prefer to reuse that object for testing.
Here's my code so far (and it is failing).
describe("Vehicle collection", function() {
beforeEach(function() {
this.vehicleCollection = new Incentives.VehiclesCollection();
this.vehicleCollection.url = '../../json/20121029.json';
this.vehicleCollection.fetch();
console.log(this.vehicleCollection);
});
it("should contain models", function() {
expect(this.vehicleCollection.length).toEqual(36);
console.log(this.vehicleCollection.length); // returns 0
});
});
When I console.log in the beforeEach method -- the console look like this ...
d {length: 0, models: Array[0], _byId: Object, _byCid: Object, url: "../../json/20121029.json"}
Curiously when I expand the object (small triangle) in Chrome Developer Tools -- my collection is completely populated with an Array of vehicle models, etc. But still my test fails:
Error: Expected 0 to equal 36
I'm wondering if I need to leverage the "waitsFor()" method?
UPDATE (with working code)
Thanks for the help!
#deven98602 -- you got me on the right track. Ultimately, this "waitsFor()" implementation finally worked. I hope this code helps others! Leave comments if this is a poor technique. Thanks!
describe("A Vehicle collection", function() {
it("should contain models", function() {
var result;
var vehicleCollection = new Incentives.VehiclesCollection();
vehicleCollection.url = '/json/20121029.json';
getCollection();
waitsFor(function() {
return result === true;
}, "to retrive all vehicles from json", 3000);
runs(function() {
expect(vehicleCollection.length).toEqual(36);
});
function getCollection() {
vehicleCollection.fetch({
success : function(){
result = true;
},
error : function () {
result = false;
}
});
}
});
});
Just glancing at your code, it looks to me like fetch has not yet populated the collection when you run the expectation.
You can use the return value from fetch to defer the expectation until the response is received using waitsFor and runs:
beforeEach(function() {
this.vehicleCollection = new Incentives.VehiclesCollection();
this.vehicleCollection.url = '../../json/20121029.json';
var deferred = this.vehicleCollection.fetch();
waitsFor(function() { return deferred.done() && true });
});
it("should contain models", function() {
runs(function() {
expect(this.vehicleCollection.length).toEqual(36);
});
});
I haven't actually tried this can't guarantee that it will work as-is, but the solution will look something like this. See this article for more on asynchronous testing with Jasmine.
the collection.fetch() is asyn call that accepts success and error callbacks
var result;
this.collection.fetch({success : function(){
result = true;
}})
waitsFor(function() {
return response !== undefined;
}, 'must be set to true', 1000);
runs(function() {
expect(this.vehicleCollection.length).toEqual(36);
console.log(this.vehicleCollection.length); // returns 0
});

Resources