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

Hi Gabby,

It appears the link you’ve provided here has died. Can you please provide an updated link? I’d like to implement your suggestion.

Thank you!

Hi, the new link is:

1 Like

Hi Gabby,

I’ve attempted to implement the code you provided (for creating a CesiumJS Viewer with a WebGL stub) in my React (TypeScript + Resium) project. However, I’m still encountering the same error.
Here is my code:
"
import getWebGLStub from “./getWebGLStub”;
import { ContextOptions, Viewer, defaultValue } from “cesium”;

declare global {
interface Window {
webglStub?: boolean;
}
}

function createViewer(container: Element | string, options: Viewer.ConstructorOptions = {}) {
options = defaultValue(options, {});
options.contextOptions = defaultValue(options.contextOptions, {}) as ContextOptions;
options.contextOptions.webgl = defaultValue(options.contextOptions.webgl, {});
if (!!window.webglStub) {
options.contextOptions.getWebGLStub = getWebGLStub;
}

return new Viewer(container, options);
}
export default createViewer;
"
Could you please assist me in identifying what I might have missed or misconfigured?

Thank you.

You can omit the line if (!!window.webglStub) {. This a command line option for configuring CesiumJS specs that you likely don’t need.

Hi Gabby,

Thanks for your previous message, and I apologize for the delayed response.

I followed your advice and removed the line ‘if (!!window.webglStub) {’ as you suggested.

function createViewer(container: Element | string, options: Viewer.ConstructorOptions = {}) {
  options = defaultValue(options, {});
  options.contextOptions = defaultValue(options.contextOptions, {}) as ContextOptions;
  options.contextOptions.webgl = defaultValue(options.contextOptions.webgl, {});
  // if (!!window.webglStub) { <--
    options.contextOptions.getWebGLStub = getWebGLStub;
  // } <--

  return new Viewer(container, options);
}

However, I’m still stuck with the same error.

If you have any more ideas or suggestions on how to tackle this issue, I’d be incredibly grateful.

Thank you.

Naftali

Hi @naftali123, could you show or explain how createViewer is used in your tests?

import { Cartesian3, Entity } from "cesium";
import { getWindowEntityPosition } from "./Position";
import createViewer from "../../test/createViewer";

describe('Position', ()=>{
    const x = 4919371.272916047;
    const y = 233079.2863174796;
    const z = 4039480.6613124954;
    const position: Cartesian3 = new Cartesian3(x, y, z);
    test('call getWindowEntityPosition without crashing', () => {
        const container: HTMLElement = document.createElement("div");
        const viewer: any = createViewer(container);
        const entity: Entity = new Entity({ id: "test", position });
        const result = getWindowEntityPosition({ entity, viewer });
        expect(result).toBeDefined();
    });
})