Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking DOCUMENT in Angular/Karma

How do you mock DOCUMENT (the shadow representation of an HTMLDocument) in Angular? The implementation is using this in the constructor:

@Inject(DOCUMENT) private document: Document

After looking at this How to inject Document in Angular 2 service I have put this in my .spec setup:

const lazyPath = 'dummy';
const pathname = `/${lazyPath}`;
const document = { location: { pathname } as Location } as Document;
beforeEachProviders(() => ([ {provide: DOCUMENT, useValue: document} ]));

But it's giving me errors:

ERROR in ./src/app/main/components/app-lazy/app-lazy.component.spec.ts
Module not found: Error: Can't resolve '@angular/core/testing/src/testing_internal' in '...'
resolve '@angular/core/testing/src/testing_internal' in '....'
  Parsed request is a module
  using description file: .../package.json (relative path: ...)
    Field 'browser' doesn't contain a valid alias configuration
    resolve as module

When I use a simple providers: [] in TestBed.configureTestingModule instead of beforeEachProviders from the testing_internal package, the component is undefined, eg not initialized properly. It only initializes in unit tests (in the non-test execution both works) when I switch from an injected document, to the window object (on which I cannot set/mock location). What can I do?

like image 789
Phil Avatar asked May 20 '19 20:05

Phil


3 Answers

I experience most likely a similar issue as @Phil. It appears that the problem is related to injecting DOCUMENT into a component.

When you mock the injected DOCUMENT, then the call on TestBed.createComponent() throws an error when internally calling document.querySelectorAll().

TestBed.createComponent() appears to be accessing the injected mocked document object. Not sure if this is a bug or intended.

I experience the issue with Angular 11 recently. Because I was too lazy to set up a new stackblitz, I reproduced it on an existing stackblitz based on Angular 8. But issue is the same there.

https://stackblitz.com/edit/jasmine-in-angular-beomut?file=src%2Fapp%2Fapp.component.spec.ts

My current solution/workaround for this issue is:

Move the logic related to documentinto a service. There it can be tested easily without calling TestBed.createComponent(). In your component you can then mock the service.

like image 149
Simon Bräuer Avatar answered Nov 05 '22 18:11

Simon Bräuer


You should avoid mocking the entire document object and mock/spy individual methods/properties on it instead.

Assuming you have the following in your component/service:

import { DOCUMENT } from '@angular/common';
...
constructor(@Inject(DOCUMENT) private document: Document) {}

You can test against the document object by injecting it inside your beforeEach

describe('SomeComponent', () => {
  let component: SomeComponent;
  let doc: Document;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [SomeComponent],
      imports: [
        RouterTestingModule,
        HttpClientTestingModule
      ]
    });
    const fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    doc = TestBed.inject(DOCUMENT); // Inject here **************
  });


  it('set document title', () => {
    component.setPageTitle('foobar'); // Assuming this component method is `this.document.title = title`
    expect(doc.title).toBe('foobar');
  });

  it('calls querySelectorAll', () => {
    const spy = spyOn(doc, 'querySelectorAll');
    component.someMethodThatQueries();
    expect(spy).toHaveBeenCalled();
  });

});
like image 34
JoeO Avatar answered Nov 05 '22 20:11

JoeO


Posting this as an answer because the formatting doesn't work in a comment.

Could you share a stackblitz if possible? When I need to inject a mock, I usually set it up like:

  // ... beginning of file

  const mockDocument = { location: { pathname } };

  beforeEach(() => TestBed.configureTestingModule({
    imports: [...],
    // Provide DOCUMENT Mock 
    providers: [
      { provide: DOCUMENT, useValue: mockDocument }
    ]
  }));

  // ...rest of file
like image 38
DJ House Avatar answered Nov 05 '22 18:11

DJ House