I have a scope function in my controller with some async functions. After everything is done it changes the state.
controller.js
$scope.test = function () {
async().then(function () {
$state.go('somewhere');
});
};
I could test it with setTimeout() but thats dirty I think.
How do I wait for the stateChange in my test?
Edit:
I want to write unit tests for the test() function but as it is no promise I need to watch for the state change in the test. But how? It works with setTimeout() but I don't want to use setTimeout() because it just doesn't feel right. Is there something like $scope.$watch for states?
test.js
...
it('test()', function (done) {
$scope.test();
setTimeout(function () { // I want this replaced with a listener for state
expect($scope.someVar).to.be.equal('value');
expect($state.current.name).to.be.equal('somewhere');
});
});
...
As I was editing the question to describe my problem I found the solution.
It's possible to listen for broadcasted events so it's possible to use
...
it('test()', function (done) {
$scope.test();
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
expect($scope.someVar).to.be.equal('value');
expect(toState.name).to.be.equal('somewhere');
});
});
...
You can use
$q.all.(promises).then(
function(){
$state.go()
});
To wait for your tasks to finish, as long as you can wrap them in a promise.
More info here.
Related
I have some global variables on my cypress test scenario.
describe('AutoLogin Test Case',function(){
beforeEach(function(){
Cypress.Cookies.preserveOnce('_session_id')
})
afterEach(function(){
cy.get('[id="ajax_working"]',{timeout:6000}).should('not.be.visible')
})
it('input login info',function(){
cy.visit('https://***********.******.com/')
cy.get('[id^=user_username]')
.type('ChrisPbacon').should('have.value','ChrisPbacon')
cy.get('[id^=user_password]')
.type('welcome123').should('have.value','welcome123')
cy.contains('Sign In Now').click()
})
})
After the test case is completed the system is gonna check for the "after each" function and look for "ajax_working"... I need to skip that check ONLY on the shown "it" test, but I still need to run it on the rest of the program. I don't wanna write the aftermath function on each test as it's cumbersome and overall not clean. Anyone got any tips?
Not sure if this will fit your problem, but one of the ways you could achieve this is to wrap afterEach hook in the same context with tests than need it using describe declaration, like this:
beforeEach(function () {
cy.log("Before each hook");
});
describe('AutoLogin Test Case',function() {
afterEach(function () {
cy.log("After each hook");
});
it('Some test', function () {
cy.log("Your first test case")
});
});
describe('AutoLogin Test Case 2',function() {
it('Some other test', function() {
cy.log("Your second test case");
});
});
Note the output which has your expected behavior, beforeEach hook called in both tests, while afterEach hook is called only on first test:
$rootScope.$on('$stateChangeStart', function (event, state) {
...
})
result -> es lint error The "$on" call should be assigned to a variable, in order to be destroyed during the $destroy event
but if i correct it like in the documentation
var unregister = $rootScope.$on('$stateChangeStart', function (event, state) {
...
})
i get "unregister is defined but never used" error
What is the best way to correct this issue?
The unregister variable that is returned by $rootScope.$on is a function that needs to be called to unregister the watch. A common use case is to call it when the current scope is destroyed:
$scope.$on('$destroy', unregister);
this is my controller:
.controller('mainController', function($http, $scope, $timeout, $sce, updateService) {
updateData = function() {
updateService.getDataA($http, $scope);
updateService.getDataB($http, $scope, $sce);
}
var updateIntervalId = setInterval(updateData, 1000);
})
Now when the user refreshes the page old requests are still being made, and I tried to cancel it by putting this under the interval call:
window.onbeforeunload = function() {
console.log('interval killed');
clearInterval(updateIntervalId);
}
the console is logging 'interval killed', so its being executed, but requests are still being made, why?
Let us do the angular way
Inject $interval service in your controller
And update your code to following
var intervalPromise = $interval(updateData, 1000);
$scope.$on('$destroy', function() { // Gets triggered when the scope is destroyed.
$interval.cancel(intervalPromise );
});
And remove window.onbeforeunload event handler.
When it comes to angularJS I would always recommend to use $interval to handle intervals, since it works better with the angularJS hidden mechanics.
Then if the event window.onbeforeunload lies outside the controller, you will never be able to access updateIntervalId since it lies inside another scope, hence you should make it global.
The changes to asynchronous testing in Jasmine 2.0 are great in many ways. However, I'm not sure I fully understand how to test asynchronous code when I have no way to bind a callback to the async methods.
How do I test against an unknown number of async events in Jasmine 2.0? Or parallel async events which do not provide a callback call.
In 1.3 I would do this:
describe("my spec", function () {
it("should check the state of a variable after unknown number of async events", function () {
// This will execute several async functions in parallel (more than 1).
// Once they are all complete the 'window.done' variable will be set to "true".
// This method does not provide a callback.
fire_parallel_async_methods();
waitsFor(function () {
// I know that once this condition is met,
// all the above async calls are done
return window.done === true;
});
runs(function () {
// Now I can run the rest of my unit tests
});
});
});
Is Tony's answer in Jasmine 2.0: refactoring out 1.3's runs() and waitsFor() the only solution?
Another use case is for event validation. For example:
describe("my spec", function () {
it("should make sure event is fired", function () {
// This element has an event binding on 'click' which changes its class.
// In this test I want to check to make sure the event does the right thing.
var $element = $('.some-element');
// Simulate a click
$element.simulate('click');
// or $element.click();
waitsFor(function () {
// Once the event is done, the element should have a new 'after-event' class
return $element.hasClass('after-event');
});
});
});
In this example, I have no way to access the event's binding, so I can't attach a callback to it. How would I validate this in Jasmine 2.0?
I was able to find a workaround using timeouts, which mimics waitsFor() behaviour:
describe("my spec", function () {
it("should make sure event is fired", function (done) {
// This element has an event binding on 'click' which changes its class.
// In this test I want to check to make sure the event does the right thing.
var $element = $('.some-element');
// Simulate a click
$element.simulate('click');
// or $element.click();
// Set a timeout to wait for the async event to go through.
// Pick a time here that will be enough. It's a little messy, but it works for cases
// where you can't attach a callback. The hardest part is figuring out how long to set
// the timeout for.
setTimeout(function () {
// Do your test here
expect($element).toHaveClass('after-event');
// Tell Jasmine the test is finished
done();
}, 500);
});
});
I could not find any other question/answer that met my needs, so here it is:
In an AngularJS (1.2.14) controller, I have an event listener that executes an ajax call to fetch some data when an event ('fetchData') is heard. After the fetch is successful, an event (called 'fetchSuccess') is broadcasted, that a directive is listening for (though that part is irrelevant).
$scope.$on('fetchData', function () {
$scope
.submitSearch()
.then(function (results) {
$scope.$broadcast('fetchSuccess');
}, function () {
$scope.$broadcast('fetchError');
});
});
So, in my test I want to do something like this (assume that 'this.scope' is a new $scope object on the controller in the test suite):
it('should broadcast "fetchSuccess"', inject(function ($rootScope) {
var scope = this.scope,
spy = chai.spy(scope, '$broadcast');
// trigger the $broadcast event that calls the fetch method
scope.$broadcast('fetchData');
$rootScope.$apply();
expect(scope.$broadcast).to.be.called.with('fetchSuccess');
}));
But I am not clear on how to listen for the $broadcast event in an assertion. I keep getting this error: AssertionError: expected function (name, args) {...}
Just to be clear, my issue is not with the functionality of the event broadcaster or the listeners during runtime; the application works as expected. The problem is with listening to the events in the test suite.
Note that the above code is just the necessary snippet that is needed for this question. In my application, there are other variables/methods that get set/called and those things test out correctly. Meaning that if I test to see if the actual fetching method gets called, or if a particular variable is being set appropriately, those tests pass.
I have tried mixing and matching the scope variables and even listening for the $broadcast via scope.$on('fetchSuccess', fn) but nothing seems to work. The fetchSuccess event doesn't seem to get emitted, or I'm not listening for it properly.
Thanks in advance,
Ryan.
So, I have found the answer to my question and it was all my fault. Though I did have to modify the way the test was written, the core problem as simple as listening for the wrong event!
But, for those that want to know what my test looked like, here is the final test (rootscope is being set to $rootScope elsewhere):
it('should broadcast a "fetchSuccess" event', function (done) {
var eventEmitted = false;
this.scope.$on('fetchSuccess', function () {
eventEmitted = true;
done();
});
this.scope.$broadcast('fetchData');
rootscope.$apply();
eventEmitted.should.be.true;
});
There was no need to spy on the $broadcast event, the AngularJS $on listener is sufficient.