Cannot load a GLTF model in a unit test

Hi,

I’m currently writing unit tests for my app and am running into some issues getting the models to load. Here is what I’m trying to achieve:

import React from 'react';
import ReactThreeTestRenderer from '@react-three/test-renderer';
import { useGLTF } from '@react-three/drei';
import MyComponent from './MyComponent';

let model;

beforeAll(() => {
  model = useGLTF('my_model.gltf').nodes;
});

test('mesh to have one child', async () => {
  const renderer = await ReactThreeTestRenderer.create(<MyComponent model={model} />);

  const mesh = renderer.scene.children[0].allChildren;
  expect(mesh.length).toBe(1);
});

Running this test yields the output of:

 FAIL  src/components/MyComponent.test.js
  ✕ mesh to have one child (1 ms)

  ● mesh to have one child

    thrown: Promise {}

       6 | let model;
       7 |
    >  8 | beforeAll(() => {
         | ^
       9 |   model = useGLTF('my_model.gltf').nodes;
      10 | });
      11 |

      at Object.<anonymous> (src/components/MyComponent.test.js:8:1

I tried using useLoader, but got similar results.

For context, MyComponent in this example is the component that takes the loaded model as a prop and applies various mesh properties and methods to it, rendering it in the process. For various reasons, useGLTF is not called inside MyComponent. Rather, it is called in a separate function that loads other models as well.

I tried many iterations, but couldn’t get anything to work. I worst comes to worst, I can always just fallback on a default Sphere or something as the model to be passed, but mainly curious as to why an approach like this is not working.

Thanks!

May be wrong here - but isn’t useGLTF required to be wrapped around in <Suspense>, which in this case it wouldn’t be ?

useGLTF is used multiple times elsewhere without issue. I have a function that uses this hook, which when called by the app returns the expected loaded models. Even calling that exact same function when running tests returns the same error as above :confused:

I’m having a similar issue whenever I’m trying to test a component that uses useGLTF or useTexture. The only way I’m able to get around the components that use those is by replacing them with their plain three.js counterparts.

  // const [earthAtmosphere] = useTexture([
  //   process.env.PUBLIC_URL + "/earth-assets/earth-atmosphere-glow.png",
  // ]);

  const earthAtmosphere = new THREE.TextureLoader().load(
    process.env.PUBLIC_URL + "/earth-assets/earth-atmosphere-glow.png"
  );

In general I would really appreciate if they added more examples of using the @react-three/test-renderer. There’s really only one example and its not much to extrapolate from

1 Like

is there any update on this? i’m also trying to unit test a simple Plane that uses useTexture, and it seems like using the plain version above works. is there any progress to get useTexture to work similarly when using the test-renderer? or any documentation related, so we know how to use it for more cases than the one available officially on the site? thanks!

to my knowlegde no threejs loader will run under node because it relies on the dom api.

mind you this isn’t a react thing, its a threejs limitation. but we do have a loader that would work GitHub - pmndrs/gltfjsx: 🎮 Turns GLTFs into JSX components and you can use it for testing, same that gltfjsx uses, but it doesn’t actually use binaries, textures, etc, it only creates the scene graph structure.

import { GLTFStructureLoader } from 'gltfjsx'
import fs from 'fs/promises'

it('should have a scene with a blue mesh', async () => {
  const loader = new GLTFStructureLoader()
  const data = await fs.readFile('./model.glb')
  const { scene } = await new Promise((res) => loader.parse(data, '', res))
  expect(() => scene.children.length).toEqual(1)
  expect(() => scene.children[0].type).toEqual('mesh')
  expect(() => scene.children[0].material.color).toEqual('blue')  
})