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?
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.
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();
});
});
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With