Skip to content

Commit

Permalink
Merge pull request #10738 from CesiumGS/update-testing-guide
Browse files Browse the repository at this point in the history
Update testing guide for Model
  • Loading branch information
j9liu authored Aug 30, 2022
2 parents 665b161 + 7e16672 commit 878374c
Showing 1 changed file with 23 additions and 44 deletions.
67 changes: 23 additions & 44 deletions Documentation/Contributors/TestingGuide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -640,13 +640,12 @@ it("Zooms to longitude, latitude, height", function () {

Here, `spyOn` is used to replace `Camera.flyTo` (prototype function on instances) with a spy. When the Geocoder is used to search for a location, the test expects that `Camera.flyTo` was called with the right arguments.

Spies can also be used on non-prototype functions. Here is an excerpt from [ModelSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/ModelSpec.js):
Spies can also be used on non-prototype functions. For example,

```javascript
it("Applies the right render state", function () {
spyOn(RenderState, "fromCache").and.callThrough();

return loadModelJson(texturedBoxModel.gltf).then(function (model) {
return loadAndZoomToModel({ gltf: gltfUrl }, scene).then(function (model) {
const rs = {
frontFace: WebGLConstants.CCW,
cull: {
Expand All @@ -657,7 +656,6 @@ it("Applies the right render state", function () {
};

expect(RenderState.fromCache).toHaveBeenCalledWith(rs);
primitives.remove(model);
});
});
```
Expand All @@ -672,7 +670,7 @@ Beware of too tightly coupling a test with an implementation; it makes engine co

Sometimes, a test requires sample data, like a CZML file or glTF model, or a service. When possible, we try to procedurally create data or mock a response in the test instead of reading a local file or making an external request. For example, [loadArrayBufferSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Core/loadArrayBufferSpec.js) uses a spy to simulate an XHR response.

When external data can't be avoided, prefer storing a small file in a subdirectory of [Specs/Data](https://github.com/CesiumGS/cesium/tree/main/Specs/Data). Avoid bloating the repo with an unnecessarily large file. Update [LICENSE.md](https://github.com/CesiumGS/cesium/blob/main/LICENSE.md) if the data requires a license or attribution. Include a README file when useful, for example, see [Specs/Data/Models/Box-Textured-Custom](https://github.com/CesiumGS/cesium/tree/main/Specs/Data/Models/Box-Textured-Custom).
When external data can't be avoided, prefer storing a small file in a subdirectory of [Specs/Data](https://github.com/CesiumGS/cesium/tree/main/Specs/Data). Avoid bloating the repo with an unnecessarily large file. Update [LICENSE.md](https://github.com/CesiumGS/cesium/blob/main/LICENSE.md) if the data requires a license or attribution. Include a README file when useful, for example, see [Specs/Data/Models/GltfLoader/BoomBox](https://github.com/CesiumGS/cesium/tree/main/Specs/Data/Models/GltfLoader/BoomBox).

Make external requests that assume the tests are being used with an Internet connection very sparingly. We anticipate being able to run the tests offline.

Expand All @@ -682,56 +680,37 @@ Make external requests that assume the tests are being used with an Internet con

For asynchronous testing, Jasmine's `it` function uses a `done` callback. For better integration with CesiumJS's asynchronous patterns, CesiumJS replaces `it` with a function that can return promises.

Here is an excerpt from [ModelSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/ModelSpec.js):
Here is a simplified example of a test from [ModelSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/Model/ModelSpec.js):

```javascript
const texturedBoxUrl = "./Data/Models/Box-Textured/CesiumTexturedBoxTest.gltf";
const texturedBoxModel;

const cesiumAirUrl = "./Data/Models/CesiumAir/Cesium_Air.gltf";
const cesiumAirModel;
const modelUrl = "./Data/Models/PBR/Box/Box.gltf";

let scene;
beforeAll(function () {
const modelPromises = [];
modelPromises.push(
loadModel(texturedBoxUrl).then(function (model) {
texturedBoxModel = model;
})
);
modelPromises.push(
loadModel(cesiumAirUrl).then(function (model) {
cesiumAirModel = model;
})
);

return Promise.all(modelPromises);
scene = createScene();
});
```

Given a model's url, `loadModel` (detailed below) returns a promise that resolves when a model is loaded. Here, `beforeAll` is used to ensure that two models, stored in suite-scoped variables, `texturedBoxModel` and `cesiumAirModel`, are loaded before any tests are run.

Here is an implementation of `loadModel`:
afterAll(function () {
scene.destroyForSpecs();
});

```javascript
function loadModelJson(gltf) {
const model = primitives.add(new Model());

return pollToPromise(
function () {
// Render scene to progressively load the model
scene.renderForSpecs();
return model.ready;
},
{ timeout: 10000 }
).then(function () {
return model;
it("renders glTF model", function () {
return loadAndZoomToModel({ gltf: modelUrl }, scene).then(function (model) {
expect(scene).toRenderAndCall(function (rgba) {
expect(rgba[0]).toBeGreaterThan(0);
expect(rgba[1]).toBeGreaterThan(0);
expect(rgba[2]).toBeGreaterThan(0);
expect(rgba[3]).toBe(255);
});
});
}
});
```

Since loading a model requires asynchronous requests and creating WebGL resources that may be spread over several frames, CesiumJS's `pollToPromise` is used to return a promise that resolves when the model is ready, which occurs by rendering the scene in an implicit loop (hence the name "poll") until `model.ready` is `true` or the `timeout` is reached.
Given a model's url and other options, [`loadAndZoomToModel`](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/Model/loadAndZoomToModel.js) loads a model, configures the camera, and returns a promise that resolves when a model's `readyPromise` resolves.

Since loading a model requires asynchronous requests and creating WebGL resources that may be spread over several frames, CesiumJS's [`pollToPromise`](https://github.com/CesiumGS/cesium/blob/main/Specs/pollToPromise.js) is used to return a promise that resolves when the model is ready, which occurs by rendering the scene in an implicit loop (hence the name "poll") until `model.readyPromise` resolves or the `timeout` is reached. `loadAndZoomToModel` uses `pollToPromise` to wait until the model is finished loading.

`pollToPromise` is used in many places where a test needs to wait for an asynchronous event before testing its expectations. Here is an excerpt from [BillboardCollectionSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/BillboardCollectionSpec.js):
`pollToPromise` is also used in many places where a test needs to wait for an asynchronous event before testing its expectations. Here is an excerpt from [BillboardCollectionSpec.js](https://github.com/CesiumGS/cesium/blob/main/Specs/Scene/BillboardCollectionSpec.js):

```javascript
it("can create a billboard using a URL", function () {
Expand Down

0 comments on commit 878374c

Please sign in to comment.