3

I have a simple acceptance test written in the modern RFC 268 format for Ember 3.0.

The test is for a page where, if the user is unauthenticated, the URL immediately redirects to /login.

...

module('Acceptance | index', function (hooks) {
  setupApplicationTest(hooks);

  test('visiting / logged out', async function(assert) {
    assert.expect(1);

    // Test hangs here forever.
    await visit('/');

    assert.equal(currentURL(), '/login');
  });
});

This test worked great using the older format with moduleForAcceptance.

Unfortunately, this test hangs forever in Ember 3.0. Am I missing something here? Is there a better way to test a redirect?

There are no errors in the console, and the addition of some console.log statements show that the await is where the test hangs.

Will Haley
  • 703
  • 1
  • 9
  • 25

3 Answers3

0

I found the reason why this was failing. I have an Ember mixin that I use to enhance all of my routes. The mixin checks for whether or not a user is authenticated, and redirects to /login as needed.

export default Mixin.create({
  session: service(),

  beforeModel() {
    this._super(...arguments);

    return new EmberPromise((resolve) => {
      if (authenticated) {
        resolve();
        return;
      }

      this.transitionTo('login');
    });
  }
});

You'll notice that I am not resolving if authenticated is falsey. That worked fine with my app and the test syntax in 2.18.

The docs say the following regarding that hook I am overriding in my mixin.

returns Any | Promise

if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way.

To me, the bit about "non-promise return values" implies that I should be able to do what I'm doing. Especially considering this worked in 2.18, but I wonder if this was one of those "wow, how did that ever work in the first place" scenarios. Clearly this syntax isn't working in 3.0. since the transition pauses forever when testing.

The answer for me was to ensure I always resolve/reject something. In this case, I had to add an explicit reject() so that the promise chain doesn't hang.

export default Mixin.create({
  session: service(),

  beforeModel() {
    this._super(...arguments);

    return new EmberPromise((resolve) => {
      if (authenticated) {
        resolve();
        return;
      }

      this.transitionTo('login');
      reject();
    });
  }
});

My test was fine. It was the mixin that needed updating in order to work properly with Ember 3.0 and the latest testing syntax.

Will Haley
  • 703
  • 1
  • 9
  • 25
0

The issue was not what your beforeModel hook resolves if user is authenticated but that your Promise does not resolve at all. You don't have to return a Promise in beforeModel hook but if you return one it will block the transition until the Promise is resolved. Since it's not clear how ember should react if another transition is called while current transition is blocked (not resolved/rejected promise), resolving or rejecting is correct behavior. Please have in mind that in a Promise return does not have any other meaning than ending your execution. It does not resolve or reject your Promise.

jelhan
  • 6,149
  • 1
  • 19
  • 35
  • When you say, "...but if you return one it will the transition until it is resolved.", do you mean "...but if you return one it will _pause_ the transition until it is resolved."? Sorry, just want to make sure I fully understand. – Will Haley Mar 30 '18 at 19:37
  • @WillHaley Yes. A _pause_ or _block_ was missing. Thanks for pointing out. – jelhan Mar 31 '18 at 08:26
0

Another reason visit() could hang is that it waits for timers like Ember.run.later() to resolve, causing a non-obvious block somewhere in the application.

AlphaGit on github summarized the issue with an example, saying:

Most of the actions that Ember.testing executes (like visit) will append to a promise that gets executed action after action in the right order. In order to pass to the next action, Ember.testing makes sure that there is nothing pending, so that the step can be considered complete and move forward.

Along with the things that are tested for, pending AJAX requests are verified, and also scheduled timers. These timers may arise from, you guessed it, Ember.run.later calls. If for any reason you would have in your code periodic Ember.run.later methods (so that one is always waiting to be excuted), it's likely that you'll face this issue.

I've faced it myself in a similar scenario: My server returns a OAuth access token with 100 hours until expired, so ember-simpleAuth registers a call close to the expiration time with Ember.run.later to refresh the token. This will, however, prevent the test from moving along. My specific situations has been fixed in further versions but any similar behavior will reproduce the issue (which is likely a conclusion of the current design of Ember.testing).

Here are a couple other examples of users running into similar issues:

jabbascript
  • 345
  • 1
  • 6
  • 13