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 document
into 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