Reinhard Männer
Reinhard Männer

Reputation: 15247

Unit tests: How to access the Scene instance of a visionOS app using immersive space?

I have a visionOS app using immersive space with RealityView. The app adds RealityKit entities to the app's Scene instance, and uses the scene.raycast function to find CollisionCastHits, see here.

I want now to write a unit test to check if the app finds the right hits.
To do so, I have to access the Scene instance to add entities, and to check if they are hit by a raycast.

But how can I access the scene instance?
I can access it e.g. after creating the RealityView via its content parameter or via @Environment(\.realityKitScene). But this is not possible in a unit test.

EDIT:

I tried to write a unit test, following the suggestion of Andy Jazz below, as far as I understood it. I tried to initialize a RealityView instance and added an entity to its content in order to get a reference to its scene via this entity.
Since the make closure of RealityView is async, one has to wait for its completion, otherwise the test would fail immediately. Waiting is done with withCheckedContinuation.
I expected that scene is stored then in its property.
Here is my test:

@MainActor @Test func test() async throws {
    var scene: RealityKit.Scene?
    await withCheckedContinuation { continuation in
        _ = RealityView(make: { content in
            print("make")
            let entity = Entity()
            content.add(entity)
            scene = entity.scene
            continuation.resume()
        })
    }
    #expect(scene != nil)       
}  

However, when I run this test, I get the log

◇ Test test() started.
SWIFT TASK CONTINUATION MISUSE: test() leaked its continuation!

I assume the reason is that RealityView has to be set up in a SwiftUI View body, and since this is not the case, continuation.resume() will never be called, which is a continuation misuse.

Upvotes: 1

Views: 148

Answers (1)

Andy Jazz
Andy Jazz

Reputation: 58533

Try using optional rvc.entities.first?.scene object (or bp.scene):

RealityView { rvc in
    let bp = try! await Entity(named: "biplane")
    bp.scale *= 10
    bp.position.z = -3.0
    bp.generateCollisionShapes(recursive: true)
    rvc.add(bp)

} update: { rvc in
    print(rvc.entities.first?.scene?.raycast(
              from: .zero, 
                to: [0, 0.5,-3]).first?.entity.name ?? "no model"
    )
    // Result:
    // "toy_biplane_bind"
}

Put withCheckedContinuation(...) function inside your RealityView's make closure and don't forget to insert a Task block for scene initialization:

import Testing
import RealityKit
import SwiftUI
@testable import MyCodeFor

struct MyCodeForTests {
    @MainActor @Test func test() async throws {
        var scene: RealityKit.Scene?
        
        _ = RealityView { content in
            await withCheckedContinuation { continuation in
                let entity = Entity()
                content.add(entity)
                
                Task {
                    scene = entity.scene
                    continuation.resume()
                }
            }
            #expect(scene != nil)
        }
    }
}

enter image description here

Upvotes: 0

Related Questions