Testing Cesium in Jest

Hi,
I’ve been playing around with testing while using Cesium in a service within an angular application. I’m currently trying to test the application using cesium as follows:

  it('should instantiate cesium viewer', () => {    
    insantiateSpy = jest.spyOn(service, 'insantiateViewer');
    service.insantiateViewer(testElement);
    expect(insantiateSpy).toBeCalledTimes(1);
  });

But I’m getting this error regarding WebGL (I’ve already tried the mocking jest web gl node package with no success).

console.error
    Error constructing CesiumWidget.
    Visit <a href="http://get.webgl.org">http://get.webgl.org</a> to verify that your web browser and hardware support WebGL.  Consider trying a different web browser or updating your video drivers.  Detailed error information is below:
    TypeError: Cannot read properties of undefined (reading '0')
    TypeError: Cannot read properties of undefined (reading '0')
        at new Context (C:\test\node_modules\cesium\Build\CesiumUnminified\index.cjs:34546:73)
        at new Scene4 (C:\test\node_modules\cesium\Build\CesiumUnminified\index.cjs:201896:19)
        at new CesiumWidget (C:\test\node_modules\cesium\Build\CesiumUnminified\index.cjs:207024:19)
        at new Viewer (C:\test\node_modules\cesium\Build\CesiumUnminified\index.cjs:213111:24)
        at CesiumService.insantiateViewer (C:\test\src\app\geo\cesium.service.ts:47:19)
        at CesiumService.<anonymous> (C:\test\node_modules\jest-mock\build\index.js:839:25)
        at C:\test\node_modules\jest-mock\build\index.js:433:39
        at CesiumService.<anonymous> (C:\test\node_modules\jest-mock\build\index.js:441:13)
        at CesiumService.mockConstructor [as insantiateViewer] (C:\test\node_modules\jest-mock\build\index.js:154:19)
        at C:\test\src\app\geo\cesium.service.spec.ts:44:13
        at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (C:\test\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:409:30)
        at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (C:\test\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:3830:43)
        at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (C:\test\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:408:56)
        at Zone.Object.<anonymous>.Zone.run (C:\test\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:169:47)
        at Object.wrappedFunc (C:\test\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:4330:34)
        at Promise.then.completed (C:\test\node_modules\jest-circus\build\utils.js:333:28)
        at new Promise (<anonymous>)
        at callAsyncCircusFn (C:\test\node_modules\jest-circus\build\utils.js:259:10)
        at _callCircusTest (C:\test\node_modules\jest-circus\build\run.js:277:40)
        at _runTest (C:\test\node_modules\jest-circus\build\run.js:209:3)
        at _runTestsForDescribeBlock (C:\test\node_modules\jest-circus\build\run.js:97:9)
        at _runTestsForDescribeBlock (C:\test\node_modules\jest-circus\build\run.js:91:9)
        at run (C:\test\node_modules\jest-circus\build\run.js:31:3)
        at runAndTransformResultsToJestFormat (C:\test\node_modules\jest-circus\build\legacy-code-todo-rewrite\jestAdapterInit.js:135:21)

Any attempt to mock Cesium seems to fall flat too, anybody have a solution to this problem?

Hey @sebheron :wave: Can you share the implementation details of the insantiateViewer method?

Sure

  instantiateViewer(element: Element): void {
    this.viewer = new Viewer(element, this.options);
    this.update = setInterval(() => this.updateViewer(), dayjs(1, 'second').millisecond());
  }

I spotted a typo in your test code which may be the cause of the problem. Your method is called instantiateViewer but in the unit test, you refer to a insantiateViewer method.

Good spot, that typo was fixed earlier today which is why the code doesn’t match up with the error.

I’ve ended up working around the error by wrapping the Viewer’s creation in a private method that I mock using jest.

So this method:

  private createViewer(element: Element): void {
    this.viewer = new Viewer(element, this.options);
  }

Is mocked like this:

createSpy = jest.spyOn(CesiumService.prototype as any, 'createViewer');
createSpy.mockImplementation(() => {});

It’s not an ideal solution, but it works as the Viewer is never created and thus the WebGL error is never thrown. It doesn’t help my code coverage so @Aristeidis_Bampakos if you have a smarter alternative I’d be open to it.

Although I am not familiar with Jest testing, I think that it is not much different from Karma and Jasmine that I use. Your initial test code should work because you are spying on the instantiateViewer method so the viewer should not be created in the first place, which is kinda weird :thinking:

According to the unit test of the createViewer method what if you could write the first one like this:

insantiateSpy = jest.spyOn(CesiumService.prototype as any, 'insantiateViewer');
insantiateSpy.mockImplementation(() => {});
service.insantiateViewer(testElement);
expect(insantiateSpy).toBeCalledTimes(1);

I am not sure if it will work (as I mentioned I am not familiar with Jest :upside_down_face:) but AFAICU it is similar.

BTW, I usually avoid involving private properties and methods in my unit tests :wink: I try to test only public ones but I can understand that this is not the case sometimes.

Hi there,

You may have trouble creating the Viewer with the default options if WebGL isn’t available in that environment, for example if you’re running your tests directly on the command line and not through an instance of the browser.

CesiumJS optionally uses a WebGL stub in its unit tests. You could probably do the same to get your tests up and running using Specs/getWebGLStub.js.

1 Like