Using GUT to test instantiating scenes via autoload singleton event bus RootScene

Godot Version

4.2

Question

I’ve been using the GUT add on to add unit tests to my project, but have hit a snag with a scene node not getting added to the tree (when running it in editor is fine). I suspect it’s an issue with a combination of Godot 4.x, GUT, autoload singletons, signals, and myself.

I’ve whittled it down to a the bare bones - so it might look like a contrived scene switcher but you can assume I use some of this plumbing/pattern for other things.

Pieces involved:

  • RootScene.tscn (main scene). A Node2D and has either of the following two scenes as a child:
  • Splash.tscn (A Node2D with a Sprite2D background. This one is a child of the RooteScene to start with.
  • MainMenu.tscn (A Node2D with a different Sprite2D background. This is the one we transition to on user input (hitting enter).
  • EventBus.gd, an autoload singleton, where all events get listed
  • GlobalStore.gd, an autoload singleton, where state that will persist accross scene changes lives.
  • test_LeaveSplash.gd - the unit test

User sees splash, user hits enter, user sees “menu”. Works in editor. Here is _on_leave_splash in RootScenen.tscn:

func _on_leave_splash():

	var splash = get_node("Splash")
	splash.queue_free()
	await splash.tree_exited

	# add the main menu scene to the root scene:
	var mainMenuScene = load("res://scenes/MainMenu.tscn")
	var mainMenuInstance = mainMenuScene.instantiate()
	mainMenuInstance.name = "MainMenu"
	add_child(mainMenuInstance, true)

Here is the test:

func test_can_leave_splash_screen():
    var rootScene
    var rootSceneInstance

    rootScene = load("res://scenes/RootScene.tscn")
    rootSceneInstance = rootScene.instantiate()
    await wait_until(func ():
        return rootSceneInstance.is_inside_tree()
    , 5)

    # these pass:
    assert_not_null(rootSceneInstance.get_node("Splash"), "Splash screen should be present.")
    assert_eq(GlobalStore.current_game_mode, GlobalStore.GAME_MODE.SPLASH_SCREEN, "Splash screen should be current game mode.")
    
    
    # send input event to leave splash screen:
    gut.p("sending input event to leave splash screen", 2)
    _sender.add_receiver(rootSceneInstance)
    _sender.action_down("start").hold_for(0.5)
    _sender.action_up("start")
    _sender.is_idle()

    await wait_until(func ():
        return GlobalStore.current_game_mode == GlobalStore.GAME_MODE.MAIN_MENU
    , 5)

    assert_true(GlobalStore.ui_loading == false, "UI should be loaded.")
    assert_true(GlobalStore.current_game_mode == GlobalStore.GAME_MODE.MAIN_MENU, "Main menu should be current game mode.")

    assert_freed(rootSceneInstance.get_node("Splash").is_inside_tree(), "Splash screen should be gone.")

   # doesn't work: (error no node named MainMenu relative to UI_Root)
   assert_not_null(rootSceneInstance.get_node("MainMenu"), "Main menu should be present.")

I’ve tried a variety of things, like having MainMenu emit an event through the ‘bus’ and use wait_for_signal but it never reaches the test. Otherwise I just get “No node named MainMenu relative to UI_Root” or data.tree is null.

Your code looks very complex.
Can the code following this code be executed?
I see you didn’t put the nodes into the tree.

await wait_until(func ():
        return rootSceneInstance.is_inside_tree()
    , 5)
1 Like

Thanks! That was what I missed. (add_child(rootSceneInstance)). Appreciate the second set of eyes. Should have just slept on it.

Ya it does look a complex at first glance! To some web developers it will look like home though!
It’s a pattern I’m borrowing from my day job. Allows you to have scenes/component swapping while maintaining state in the background (nice for options menus while pausing etc) and decoupling between UI and state. Useful also if you want to have multiple views or layouts for the same state as well.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.