ember.jsqunitember-qunitember-octane

setApplication breaks ember-qunit test context


I recently upgraded an Ember app from 2.18 to 3.13 which went smoothly. Today I tried to add an acceptance test for the first time (only had integration / unit tests before this) but the test is failing on the first line:

import { module, test } from "qunit";
import { visit, currentURL } from "@ember/test-helpers";
import { setupApplicationTest } from "ember-qunit";

module("Acceptance | some route", function(hooks) {
  setupApplicationTest(hooks);

  test("visiting /some-route", async function(assert) {
    await visit("/some-route"); // <----- error thrown here

    assert.equal(currentURL(), "/some-route");
  });
});

I'm seeing a couple errors (in this order):

Source:     
TypeError: Cannot read property 'lookup' of undefined
    at Object.initialize (http://localhost:4200/assets/my-app.js:10312:28)
    at http://localhost:4200/assets/vendor.js:61627:21
    at Vertices.each (http://localhost:4200/assets/vendor.js:80243:9)
    at Vertices.walk (http://localhost:4200/assets/vendor.js:80157:12)
    at DAG.each (http://localhost:4200/assets/vendor.js:80087:22)
    at DAG.topsort (http://localhost:4200/assets/vendor.js:80095:12)
    at Class._runInitializer (http://localhost:4200/assets/vendor.js:61653:13)
    at Class.runInitializers (http://localhost:4200/assets/vendor.js:61625:12)
    at Class._bootSync (http://localhost:4200/assets/vendor.js:59923:14)
    at Class.boot (http://localhost:4200/assets/vendor.js:59890:14)


Source:     
Error: Cannot call `visit` without having first called `setupApplicationContext`.
    at visit (http://localhost:4200/assets/test-support.js:44177:13)
    at Object._callee$ (http://localhost:4200/assets/tests.js:23:47)
    at tryCatch (http://localhost:4200/assets/vendor.js:12365:40)
    at Generator.invoke [as _invoke] (http://localhost:4200/assets/vendor.js:12591:22)
    at Generator.prototype.<computed> [as next] (http://localhost:4200/assets/vendor.js:12417:21)
    at asyncGeneratorStep (http://localhost:4200/assets/tests.js:6:105)
    at _next (http://localhost:4200/assets/tests.js:8:196)
    at http://localhost:4200/assets/tests.js:8:366
    at new Promise (<anonymous>)
    at Object.<anonymous> (http://localhost:4200/assets/tests.js:8:99)

After a little digging, it looks like something is setting the test context incorrectly. It's just an empty object:

enter image description here

Thus, isApplicationTestContext(context) returns false and the second error gets thrown. I'm guessing the first error is thrown because the app has some initializers that perform lookups.

In an effort to add this acceptance test I also updated the test-helper.js file to the following:

import Application from "../app";
import config from "../config/environment";
import { setApplication } from "@ember/test-helpers";
import { start } from "ember-qunit";

setApplication(Application.create(config.APP));

start();

With the above file, all tests are failing so it seems that setApplication is causing the test context to be set incorrectly? The old test-helper.js file was like so:

import resolver from "./helpers/resolver";
import { setResolver } from "@ember/test-helpers";
import { start } from "ember-cli-qunit";

setResolver(resolver);
start();

I've tried re-adding the setResolver call but it doesn't make a difference. Has anyone else run into these issues with the new ember-qunit syntax or could maybe see what I'm doing wrong? Also, I've set autoboot = false; in the environment.js file which didn't make a difference. The test suite also has one or two tests that are still written in the older ember-qunit syntax. Any help would be appreciated!


Solution

  • First, some back story:

    Our application utilizes a 3rd party library for metrics and another 3rd party library for feature flags. Each library has its own service but we need the metrics service to be initialized before the feature flag service can be initialized because we want to link the analytic user data to get the right feature flags for each user. The feature flag checks are done all throughout the application so a race condition arose between when a feature flag check would occur and when the analytics file was loaded in a script tag on the webpage.

    Now the solution:

    The reason why this error was popping up:

    Source:     
    TypeError: Cannot read property 'lookup' of undefined
        at Object.initialize (http://localhost:4200/assets/my-app.js:10312:28)
        at http://localhost:4200/assets/vendor.js:61627:21
        at Vertices.each (http://localhost:4200/assets/vendor.js:80243:9)
        at Vertices.walk (http://localhost:4200/assets/vendor.js:80157:12)
        at DAG.each (http://localhost:4200/assets/vendor.js:80087:22)
        at DAG.topsort (http://localhost:4200/assets/vendor.js:80095:12)
        at Class._runInitializer (http://localhost:4200/assets/vendor.js:61653:13)
        at Class.runInitializers (http://localhost:4200/assets/vendor.js:61625:12)
        at Class._bootSync (http://localhost:4200/assets/vendor.js:59923:14)
        at Class.boot (http://localhost:4200/assets/vendor.js:59890:14)
    

    is because the application has an application initializer that was utilizing private Ember APIs to perform a lookup from the container. The initializer was performing a lookup to implicitly initialize the metrics service before retrieving analytic data and then initializing the feature flag service.

    export function initialize(application) {
      const container = application.__container__; // <-- undefined
      const lookup = container.lookup.bind(application.__container__); // <-- error!
      ...
    }
    

    What's strange is the above code works in the development and production environments. This change seems to be a result of the deprecations / rearrangement of internal Ember APIs particularly related to the container. See this page for more information.

    In order to access the container in an application initializer it must be done from an application instance (i.e., an instance initializer). This might have been an acceptable solution, but I wasn't 100% sure if we could defer application readiness from an application instance initializer.

    For more reading on the application instance initializers, see here.

    Instead the code was moved to the application route's beforeModel() hook. I also added an instance initializer for the metrics service which resulted in a faster application load time.

    After moving the initializer code to the application route the application is now being built successfully and acceptance tests work like a charm. :)