Handling several event listeners - unit-testing

Update: here's a fiddle of my problem. The tests pass once, and fail the next time:
http://jsfiddle.net/samselikoff/hhk6u/4/
The problem is departments has events.on("userSet:company"), so both variables respond to the event.
This is a general question about unit testing. In my app, a certain event is fired, and several other pieces of my app listen for this event. I'd like to unit test each piece separately, since they are performing different functions; but to do this, I have to fire off the event in each test.
This causes problems, since the first test must fire off the event, triggering the listeners in the other tests. How can I keep my tests atomic while still testing multiple event listeners?
(I am using QUnit, but I think this is a more general unit-testing question).
Answer:
Jeferson is correct. One easy way to solve this, is to use events.once instead of events.on. This way you clean up your events from each test.

All your calls to async methods should be tested using "asyncTest" methods and making sure you wrap your calls in other functions that calls QUnit.start() when the assertions data are ready to be collected and analyzed.
I updated your JSFiddle with working code: http://jsfiddle.net/hhk6u/8/
The new code is:
QUnit.config.autostart = false;
QUnit.config.testTimeOut = 1000;
asyncTest('Some test that needs companies.', function() {
function getCompanies() {
var companies = new Companies();
ok(1);
start();
}
setTimeout(getCompanies, 500);
});
asyncTest('Some other async test that triggers a listener in companies.', function() {
var companies = new Companies();
events.trigger("userSet:company", { name: "Acme", id: 1 });
stop();
events.on('fetched:departments', function(response) {
console.log(response);
deepEqual(response, [1, 2, 3]);
start();
});
});
See my answer in this other question for more details:
Test fails then succeeds
Hope this helps you!

Related

Ember concurrency timeout hanging in qunit

In Ember I have a component that starts a never-ending poll to keep some data up to date. Like so:
export default Component.extend({
pollTask: task(function * () {
while(true) {
yield timeout(this.get('pollRate'));
this.fetchSomeData();
}
}).on('init')
})
This causes a preexisting acceptance test to get stuck in this task and run forever, even though it should be run asynchronously. The test looks like this:
test('my test', async function(assert) {
mockFindRecord(make('fetched-model'));
await visit('/mypage'); // gets stuck here
await typeIn('input', 'abc123');
assert.ok(somethingAboutThePage);
});
I thought at first that I had mocked the request wrong and that the test was just timing out, but it was in fact correctly polling data. Removing this task makes the acceptance test finish normally.
Testing this manually seems to work fine, and nothing gets stuck. Why is this happening and what is the right way to address this?
Saw Unit testing ember-concurrency tasks and yields but it doesn't really help since it only deals with unit tests.
You're not doing anything wrong and this is a common gotcha with ember-concurrency. Ember-concurrency's timeout() function relies on Ember.run.later() to create the timeout and fortunately or unfortunately, Ember's test suite is aware of all timers created with Ember.run.later() and will wait for all timers to settle before letting the test continue. Since your code is using an infinite loop your timers will never settle so the test hangs. There's a nice little section about testing asynchronous code in the Ember guides here.
There's a section in the ember-concurrency docs about this exact problem here. I recommend you look through it to see their recommendations on how to tackle this although it seems as if there's no real elegant solution at the time.
The quickest and probably easiest way to get this to not hang would be to throw in a check to see if the app is being tested (gross, I know):
pollTask: task(function * () {
while(true) {
yield timeout(this.get('pollRate'));
this.fetchSomeData();
if (Ember.testing) return; // stop polling to prevent tests from hanging
}
}).on('init')
You can also try to throw in a call to Ember.run.cancelTimers() in your tests/helpers/start-app.js file (another suggestion in that section):
window._breakTimerLoopsId = Ember.run.later(() => {
Ember.run.cancelTimers();
}, 500);
But it doesn't seem to appear in the API docs so I personally wouldn't rely on it.

Wrapper around TASKs in C#

I am using tasks in WinForms (.NET 4.0) to perform lengthy operations like WCF call. Application is already in product with heavy use of Tasks (almost all the methods which uses Tasks are void).
During the unit testing we have used AutoResetEvents (in actual code) to find out when the given task is completed then perform assert.
This gives me a thought that almost all the AutoResetEvent are waste of effort. They are just fulfilling unit testing needs, nothing else.
Can we create a wrapper around Tasks likewise when actual code run... they should work in background and in case of unit testing they should be synchronous.
Similar to below link for BackgroundWorker.
http://si-w.co.uk/blog/2009/09/11/unit-testing-code-that-uses-a-backgroundworker/
Why can't you simply use the continuation for tasks in your wrapper, like this:
var task = ...
task.ContinueWith(t => check task results here)
Also, unit tests can be marked as async, if they have a return type Task, so you can use an await there, and after that do your asserts:
[Test]
public async Task SynchronizeTestWithRecurringOperationViaAwait()
{
var sut = new SystemUnderTest();
// Execute code to set up timer with 1 sec delay and interval.
var firstNotification = sut.StartRecurring();
// Wait that operation has finished two times.
var secondNotification = await firstNotification.GetNext();
await secondNotification.GetNext();
// Assert outcome.
Assert.AreEqual("Init Poll Poll", sut.Message);
}
Another approach (from the same article) is to use a custom task scheduler, which will be synchronous in case of unit testing:
[Test]
public void TestCodeSynchronously()
{
var dts = new DeterministicTaskScheduler();
var sut = new SystemUnderTest(dts);
// Execute code to schedule first operation and return immediately.
sut.StartAsynchronousOperation();
// Execute all operations on the current thread.
dts.RunTasksUntilIdle();
// Assert outcome of the two operations.
Assert.AreEqual("Init Work1 Work2", sut.Message);
}
Same MSDN magazine contains nice article about best practices for async unit testing. Also async void should be used only as an event handler, all other methods should have async Task signature.

Simulate keydown on document for JEST unit testing

Using JEST to unit test a component that has a keydown listener attached to the document.
How can I test this in JEST? How do I simulate the keydown event on the document? I need the event listener to be on the document since it is supposed to respond the keyboard action irrespective of the focussed element.
EDIT: The question here is about simulating the event on the document or the document.body. All the examples are regarding an actual DOM node, that works fine but the document does not.
Currently trying to do this:
TestUtils.Simulate.keyDown(document, {keyCode : 37}); // handler not invoked
I had the exact same problem and unfortunately couldn't find any details on how to solve this using TestUtils.Simulate. However this issue gave me the idea of simply calling .dispatchEvent with a KeyboardEvent inside my jest tests directly:
var event = new KeyboardEvent('keydown', {'keyCode': 37});
document.dispatchEvent(event);
You can find details on the KeyboardEvent here: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
You can also replace the whole document.eventListener with mock function before you mount the component:
let events;
document.addEventListener = jest.fn((event, cb) => {
events[event] = cb;
});
And simulate event by calling events[event] after mounting, e.g.:
events.keydown({ keyCode: 37 })
Also, it's quite comfortable to do first part in beforeEach() function if you have many tests dealing with DOM events.
Following #Iris Schaffer answer,
If your code makes use of ctrl/alt/shift keys, you will need to init them, as well as mocking implementation of getModifierState method on KeyboardEvent
const keyboardEvent = new KeyboardEvent('keydown', { keyCode, shiftKey, altKey, ctrlKey });
jest.spyOn(keyboardEvent, 'getModifierState').mockImplementation((modifier) => {
switch (modifier) {
case 'Alt':
return altKey;
case 'Control':
return ctrlKey;
case 'Shift':
return shiftKey;
}
});

calling set on destroyed object setInterval in acceptance tests

I know that there are like 5 topics with the same name here, but they seems to be unrelated to my case: I'm getting this error in acceptance tests, and the reason for that is the countdown component built with Ember.run.later.
Maybe someone know how to cure that?
Just should stop timer upon element destruction. Ember, of course, doesn't handle that by himself.
startCountdown: function() {
let handler;
//...
handler = setInterval(...);
this.set('handler', handler);
}
teardown: function() {
clearInterval(this.get('handler'));
}.on('willDestroyElement')

What is the proper way in ember to use scheduleOnce that is test friendly?

Often, we'll need to schedule an update after rendering completes in reaction to a property change. Here is an example:
page_changed: function() {
Ember.run.scheduleOnce('afterRender', this, this.scroll);
}.observes('current_page')
This doesn't work for testing since there is no runloop at the time scheduleOnce is called. We can simply wrap scheduleOnce in an Ember.run
page_changed: function() {
Ember.run(function() {
Ember.run.scheduleOnce('afterRender', that, that.scroll);
});
).observes('current_page')
..but I'm being told that's not the right way to go about it. I thought I'd reach out and get some other ideas.
For reference here is the issue that I opened up in ember.js#10536
Looks like this is the way to do it according to #StefanPenner's comment. Instead of modifying the app code itself just wrap the render call with an Ember.run
test('it renders', function() {
expect(2);
var component = this.subject();
var that = this;
equal(component._state, 'preRender');
// Wrapping render in Ember.run
Ember.run(function() {
that.render();
});
equal(component._state, 'inDOM');
});
the entry point into your application in the test needs the Ember.run. For example
test('foo', function() {
user.set('foo', 1); // may have side-affects
});
test('foo', function() {
Ember.run(function() {
user.set('foo', 1);
});
});
Why is this needed? Ember.run wraps the root of all call-stacks that are triggered by click/user-actions/ajax etc. Why? The run-loop is what allows ember to batch
When writing unit-tests, we are "faking" the user or network actions, it isn't obvious to ember what groups of changes you want to "Batch" together.
We can think of Ember.run as a way to create a batch or transactions of changes. Ultimately we use this, to batch DOM reads/writes to interact with the DOM in an ideal way.
Although maybe frustrating until you get the hang of it, it is a great way to create concise and deterministic tests. For example:
test('a series of grouped things', function() {
Ember.run(function() {
component.set('firstName', 'Stef');
component.set('lastName', 'Penner');
// DOM isn't updated yet
});
// DOM is updated;
// assert the DOM is updated
Ember.run(function() {
component.set('firstName', 'Kris');
component.set('lastName', 'Selden');
// DOM isn't updated yet, (its still Stef Penner)
});
// DOM is once again updated, its now Kris Selden.
});
Why is this cool? Ember gives us fine-grained control, which lets us not only very easily test aspects of our app in isolation, but also test sequences of what may be user or ajax pushed based actions, without needing to incorporate those aspects.
As time goes on, we hope to improve the test helpers and clarity around this.
..but I'm being told that's not the right way to go about it.
The reason is that you need the Ember.run call when the callback is executed, not when it's scheduled. Like this:
Ember.run.scheduleOnce('afterRender', function() {
Ember.run(function() {
that.scroll();
});
});
In your code, Ember.run is being called when the callback is scheduled. This is likely unnecessary as you're probably already in the run loop. My version ensures that when the callback is called, even if the run loop finished long ago, that another run loop is started and that.scroll() is called within the run loop. Does that makes sense?