Testing: how to assert between two sequential promises/run.laters? How to skip `run.later` waiting in tests? - ember.js

Here's a simple component:
App.FooBarComponent = Ember.Component.extend({
tagName: "button",
status: "Ready",
revertStatusPeriodMs: 2000,
click: function() {
this.set('status', 'Pending');
// A fake ajax
this.run()
.then( function() {
this.updateStatus('Finished');
}.bind(this))
.catch( function() {
this.updateStatus('Error');
}.bind(this));
},
run: function() {
return new Ember.RSVP.Promise( function(resolve) {
Ember.run.later( function() {
resolve();
}, 500);
});
},
updateStatus: function(statusText) {
this.set('status', statusText);
var periodMs = this.get('revertStatusPeriodMs') || 1000;
Ember.run.later( function() {
this.set('status', 'Ready');
}.bind(this), periodMs);
}
});
It does a simple thing: when clicked, it displays some text. Later replaces the text with another one. Even later, it reverts the text to the initial one.
The code works fine. My problem is that i'm unable to write a test for it.
test('clicking the button should set the label', function(assert) {
expect(4);
visit('/');
assert.equal( find('button').text().trim(), 'Ready', 'Should initially be "Ready"');
andThen(function() {
click('button');
assert.equal( find('button').text().trim(), 'Pending', 'Should "Pending" right after click');
});
// This one fires too late!
andThen(function() {
assert.equal( find('button').text().trim(), 'Finished', 'Should become "Finished" after promise fulfills');
});
andThen(function() {
assert.equal( find('button').text().trim(), 'Ready', 'Should eventually return to the "Ready" state');
});
});
I have two problems:
I'm unable to test the Finished state. It seems that andThen waits for all promises and run.laters to finish, while i want to test an intermediate state. How do i run an assertion between two sequential promises/run.laters?
Times can be long, and tests can take forever to complete. I generally don't mind refactoring the code for better testability, but i refuse to adjust times in the app based on the environment (e. g. 2000 for dev/prod, 0 for test). Instead, i would like to use a timer mock or some other solution.
I've tried Sinon and failed: when i mock the timer, andThen never returns. Neither of these solutions helped me.
JSBin: http://emberjs.jsbin.com/fohava/2/edit?html,js,output (Sinon is included)

None of your test code seems to use asynch – which would seem to be crucial since without anything to distinguish them, the last 2 tests will execute in the same tick (tests 3 & 4 can't both be true at the same time). I'm not familiar with QUnit, but I did find it's async method, which would seem to be pertinent. However when I tried calling assert.async, it blew up. I tried updating QUnit 1.17, still no dice.
So I don't have a solution for you. The problem, though, is that Ember will only execute both andThen tests once all the asynchronous execution has finished – this means that test 3 will never be true.

I did some code where I basically got it working but it seems really finicky and not something you would want to be do be doing..
I did this a few days ago but never replied because I wasn't happy with it - it doesn't give you enough control..
The reason I'm replying now however is because I've found a PR that may be of use to you: PR 10463
I didn't look into too much detail but apparently it "allows custom async test helpers to pause execution by returning a promise"
I'm not sure what version of Ember you're using - but if you can hold out for this PR it may help you..

Related

Testing stateful classes

I have a class that can load an image, close it or rename it (at least for now, there will be more operations with opened file in future).
I've implemented it with Facade pattern (I think so).
When I tried to write tests for it, I quickly noticed that I need to test it with different pre-conditions (for example, when image was loaded before calling some method). And the amount of tests are huge and will grow very quickly when I add new operations. As I understand these are not unit tests, but rather are end-to-end tests.
I am new to TDD, so can anyone tell me is it normal practice to have such complicated tests?
I'm expecting answers about too much responsibility of my class, but lets think about alternatives:
// pseudo-code
// #1 - my way
function onRenameButtonClick(newName) {
imageFacade.rename(newName)
}
// #2 - the other way
function onRenameButtonClick(newName) {
if (imageController.isOpened()) {
imageRenamer.rename(imageController.image, newName)
}
}
In the end I still need to test correct behavior for #2, and it still will involve using different pre-conditions.
How do you deal with such cases? Is it normal or I'm doing something wrong?
P. S. here is a skeleton of my facade class, note that in config there are pure functions, that do actual work, and actions are stitching these pure functions and trigger events depending on the state.
function makeImageWTF(loader, renamer) {
return {
state: {
image: null,
filePath: null,
isLoading: false
},
config: {
loader,
renamer
},
triggers: {
opening: new Bus(),
opened: new Bus(),
closed: new Bus(),
renamed: new Bus(),
error: new Bus()
},
actions: {
open: function(path) {},
close: function() {},
rename: function(newName) {}
}
}
}
And here is a skeleton of tests
describe('ImageWTF', function() {
describe('initial state', function() {
it('should be that', function() {
var wtf = makeImageWTF()
assert.deepEqual({
image: null,
filePath: null,
isLoading: false,
}, wtf.state)
})
})
describe('#open(path)', function() {
it('sets isLoading')
it('calls config.loader with path')
it('sets image, fileName')
it('triggers loading, loaded or error')
it('clears isLoading')
})
describe('#close()', function() {
describe('when image is opened', function() {
it('resets image')
it('triggers closed')
})
describe('when image is NOT opened', function() {
it('triggers error')
})
})
describe('#rename()', function() {
describe('when image is opened', function() {
it('calls config.renamer with oldName and newName')
it('sets fileName')
it('triggers renamed')
})
describe('when image is NOT opened', function() {
it('triggers error')
})
})
})
(Unit) Test are just like production code - they can get complicated. But the goal, of course, should be to keep them as simple as possible.
If you do not have test for your code yet I suggest you start writing test to cover the most important use cases. Once you have those working in place you can refactor them. But for me the main focus would be to get test in place, you will learn a lot going down that path.
I would not care to much if they are not "unit test" from the start, change them to fit your application.
Try not to couple the tests to hard with the production code, since you want the flexibility to refactor the production code without changing the tests at the same time (and the other way around, of course). Unit test for me is really about making it easy and fast to change and refactor code. They will not catch all bugs or behavior in you application - for that you need to focus on other kinds of tests as well.

Ember Test: input focus

I really don't understand the chain of events that's happening here. Trying to follow the guide as well as possible. I have:
test('Tab focus', function(assert) {
visit('/demo/form');
click('input[type=text]');
andThen(function() {
assert.equal(
find('input[type=text]').css('borderTopColor'), 'rgb(0, 125, 164)', 'Text input has focus'
);
});
});
only to have it fail:
There are no transitions on the color change, and if I hit rerun, it DOES pass.
for anyone still looking for an answer - you have to trigger "focus" event manually in your test:
triggerEvent(<alement selector>, 'focus');
more info: https://guides.emberjs.com/v2.14.0/testing/acceptance/#toc_asynchronous-helpers

Ember run later in coffeescript not working correctly

I am pretty new to Ember so I am totally stumped by this. The run later appears to work however it seems to ignore the fact that it is supposed to only run once every 5 seconds to update the status, it actually seems to just continually and immediately keep calling the getServers method and then immediately call itself (startWatchingStatus) again. Because of this code the page never actually gets rendered. I must be doing something wrong in this code:
App.ServersController = Ember.ArrayController.extend(
startWatchingStatus: () ->
controller = #
controller.getServers()
Ember.run.later(controller, () ->
controller.startWatchingStatus()
, 5000)
getServers: () ->
App.ServerObject.getServers().done((data) ->
controller.set('content', data)
)
init: () ->
#_super()
controller = #
controller.startWatchingStatus()
You are misinterpreting the usage of Ember.run.later, it will wait 5000ms and then execute your method, it won't keep repeating it for you. For that you could either chain the calls to Ember.run.later like this (sorry, don't know coffescript but you'll get the idea):
var callback = () => {
this.getServers();
Ember.run.later(this, callback, 5000);
};
Ember.run.later(controller, callback, 5000);
Or you could go with a simple window.setInterval and wrap it within a runloop inside.
window.setInterval(() => {
Ember.run(() => {
controller.getServers();
});
}, 5000);
The second one might be easier if you need to be cancel the interval later on but it's up to you which one you prefer.

Ember intermittent acceptance test failures that pass when run alone

I'm hoping to get some clues about why I have intermittent test failures. I have an acceptance test that fails when I run the entire module, but all of the individual tests pass when they are run alone. The issue seems to have something to do with using a loop around assertions because I have two different tests that fail and both have a forEach loop something like this:
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from "dwellconnect-app/tests/helpers/start-app";
var things;
module('Acceptance | something', {
beforeEach: function() {
this.application = startApp();
things = [
Ember.Object({someProperty: '1'}),
Ember.Object({someProperty: '2'}),
Ember.Object({someProperty: '3'}),
Ember.Object({someProperty: '4'}),
]
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('Something', function(assert) {
assert.expect(4);
visit('/something');
andThen(function() {
// Check that each thing is on the page
things.forEach(function(thing) {
var someSelector = 'span:contains("' + thing.someProperty + '")';
assert.equal(find(someSelector).length, 1, 'Shows each thing');
});
});
});
First of all, is there something wrong with this approach and if so, what is the correct way to build a set of assertions around an array of data. If not, what else could be causing the issue? Even if you can only provide possible scenarios for intermittent failures, it might lead me in the right direction. Right now there are no errors and this is making me crazy. I'm not sure Sherlock Holmes could find this one.

Ember.js - login example: Ember.run.later

I copied this login example for my own needs. It runs fine. But I am asking myself: why do I need the Ember.run.later(this, this._serverLogin, 100); line? Like the comment said it is only for simulating the delay. Ok. But if I change it to this:
// Create the login controller
MyApp.loginController = Ember.Object.create({
username: '',
password: '',
isError: false,
tryLogin: function() {
if(this.get('username') === MyApp.USERNAME &&
this.get('password') === MyApp.PASSWORD) {
this.set('isError', false);
this.set('username', '');
this.set('password', '');
MyApp.stateManager.send('loginSuccess');
} else {
this.set('isError', true);
MyApp.stateManager.send('loginFail');
}
},
});
without Ember.run.later(this, this._serverLogin, 100);, I get Uncaught Error: <Ember.StateManager:ember270> could not respond to event loginSuccess in state loggedOut.awaitingCredentials. So I thought probably I need this delay to get the stateManager changed before or somethign like that. But when I run the old code with Ember.run.later(this, this._serverLogin, 0); it still works. So, whats different? The documentation of ember didnt gave any hints.
It's because your StateManager is still in the early state setup process when calling a sendEvent (loginSuccess/loginFailed).
By delaying the event sending w/ Ember.run.later, your code is processed in a next run loop, and the state is properly setup.
That being said, you are using Ember in a very old fashion. You should have a look at the state-of-the-art way to manage app routes.