Search code examples
angularunit-testingmockingangular-elementsangular-jest

How to mock Angular external Javascript object


I have an angular application which only purpose is to build @angular/elements (no index.html, no app.component, etc.).

Those elements are used in an .NET Mvc app which provides two kind of things I'm trying to mock in my unit tests:

1 - External librairies

For instance, JQuery, lodash etc.

2 - Client-side Framework "foo"

Inside the .NET MVC view which includes my web elements, the MVC application creates an object called foo. This object isn't an ES6 module. It is created manually with a beautiful foo = new Foo({ someOptionsGeneratedFromServerSide});

Both case are used inside my web elements. For instance, a component will use the function foo.displayAlert('With a message'). The only solution I found to make my angular application to comply with that is to add the following:

// tslint:disable-next-line: max-line-length no-reference
/// <reference path='path/to/the/definition.d.ts' />

I know this is quite un-orthodox.

The Angular application works fine until I'm trying to do some unit testing. Note: it cannot work alone (ng serve is useless as expected).

An example

it('should render title', () => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const compiled = fixture.nativeElement;
  expect(compiled.querySelector('.content span').textContent).toContain('ng-jest app is running!');
});

For this component / hook:

ngOnInit() {
  foo.displayAlert('With a message');
}

Will crash with the following (quite expected) message:

AppComponent › should render title
ReferenceError: foo is not defined

I tried to explicitly mock the object before the test:

  let foo: any;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
    foo = {
      displayAlert: Function
    };
  }));

No luck so far. I did a lot of attempts of mocking inside the x.spect.ts about this object without results.

I tried to create another Angular application with an index.html which includes all resources like the MVC view but it failed.

Final notes

For 3rd parties like JQuery / lodash, I do not wish to add them in my Angular project like: npm i -D lodash as I don't want them to be included in my vendor.js file. Those libraries are provided by the .Net Mvc application and this is fine. This applies to the foo object as well. Finally, I tried with (Jest) & without (Karma) a headless browser for the tests.

Any help would be really appreciated!

Edit 27/03/2020

Please, save me !

Edit 28/03/2020

Tried jasmine.createSpyObject & spyOn, no luck either :(


Solution

  • Complicated setup, you're not making it easy for yourself.

    However, it seems you just need to fake foo as global variable since the global scope is where your component expects the variable to be. When you declare let foo inside an ECMAScript module, you're creating a variable scoped to that file which your component can't access.

    beforeEach(() => {
      window.foo = {
        displayAlert: () => {},
      };
    });
    

    This will probably cause you to wrestle with TypeScript. A quickfix is this

    beforeEach(() => {
      (window as any).foo = {
        displayAlert: () => {},
      };
    });