Skip to content

Instantly share code, notes, and snippets.

@indiesquidge
Created February 9, 2016 23:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indiesquidge/3b5ab88d0064a09a9c9e to your computer and use it in GitHub Desktop.
Save indiesquidge/3b5ab88d0064a09a9c9e to your computer and use it in GitHub Desktop.
Module Dependencies and Compiling in Unit Tests for Angular 1

Module Dependencies and Compiling in Unit Tests for Angular 1

I was writing a test that would click on a checkbox element

describe(`checkbox unchecked`, () => {
    it(`should remove the answer if the checkbox is unchecked`, () => {
        var form = angular.element(`<form name="form">
                                        <div gi-checkbox-optional="answer"
                                             gi-form-model="form">
                                        </div>
                                    </form>`);
        checkboxOptionalScope.answer = 2;
        $compile(form)(checkboxOptionalScope);
        checkboxOptionalScope.$digest();
        checkboxOptionalElement = form.children().eq(0);
        var giCheckboxElement = checkboxOptionalElement.children()[0];
        giCheckboxElement.click();

        expect(checkboxOptionalScope.answer).toBe(null);
    });
});

Context

  • gi-checkbox-optional is an app.brief directive.
  • gi-checkbox is an app.common directive that is prepended as the first child of gi-checkbox-optional when it is compiled.
  • For more information on the gi-checkbox-optional directive, please click here

First problem: the nested directive, gi-checkbox, was never actually getting compiled.

The reason this was happening is because the app.brief module was not including app.common as a dependency explicitly. That's not to say that gi-checkbox was the first app.common directive to be used by app.brief, but it does mean this was the first time a test was written to expose the missing connection.

To understand this better we need to look at how our whole app is set up. We have many different modules––app, app.common, app.brief, etc.––each with their own custom directives, filters, and services. Upon creation, each module does not list every other module as dependency. Rather, the app module includes all the other modules as dependencies and everything is set up and injected at runtime. This is what allows for, say, an app.common directive to be used by an app.brief directive without having to explicitly inject the app.common module.

However, all of this falls to pieces when you are unit testing a specific module. Since you aren't loading up your entire app for a unit test (at least you shouldn't be)––you are only loading the module you are testing––it exposes any missing links. I take that back, it silently fails because of the missing links, leaving you banging your head against the wall.

In retrospect, it makes sense why it fails silently for a directive that's defined in another module. In the Angular 1.x world, there are no explicit definitions for what directives or services or filters a file is using. This makes it impossible for the Angular compiler to know what parts of the DOM should be treated and watched as directives, and which parts are just part of the HTML. The way that the compiler does know what to treat as a directive is based on what is defined within a module. Therefore, if you are only loading one module, the compiler will only know about the directives and services that are defined by that included module; it will simply skip over and not add watchers to the elements and attributes that are not defined in the included module. (As an aside, much of this ambiguity has been taken care of with imports in Angular 2, allowing you to definitively state what things that file will be using, but that is beyond the scope of this post.)

All of this goes to say that the fix was to add the app.common module as a dependency for app.brief (source).

You do not need to change the module defined in your test file though. My first (failed) attempt to solve this issue was to just add the app.common module in the beforeEach function alongside app.brief. I figured that I just needed to specify all the modules I wanted to work with and the compiler would take both of those into account when running through the code. While this did include both the modules in the test file, the reason this doesn't work is because it didn't allow the modules to talk to each other. The gi-checkbox directive (defined in app.common) was dependent on the gi-checkbox-optional directive (defined in app.brief). Including the two modules in the beforeEach function gave the compiler acknowledgment of them, but it did not let them depend on each other.

One thing that would be nice (and perhaps this is possible with mocks in Angular 2) is to simply include the pieces you wish to test at the top of the file. app.common contains––by definition––functionality that is “commonly” shared among other modules. This makes it quite large, and therefore including the whole module in the test file when I all need is a single directive seems like overkill.

Second problem was that the changed scope value was not being rendered

This problem was far easier to understand and fix. It is still a bit unclear to me, but––based on some posts that I could find and what Jeff and I had talked about––the way I understand it is that when you call compile on some element with some scope, ...

@Phylodome
Copy link

Angular's implicitly defined module system strikes again...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment