class_name GdUnitSpyTest extends GdUnitTestSuite func test_spy_instance_id_is_unique(): var m1 = spy(RefCounted.new()) var m2 = spy(RefCounted.new()) # test the internal instance id is unique assert_that(m1.__instance_id()).is_not_equal(m2.__instance_id()) assert_object(m1).is_not_same(m2) func test_cant_spy_is_not_a_instance(): # returns null because spy needs an 'real' instance to by spy checked var spy_node = spy(Node) assert_object(spy_node).is_null() func test_spy_on_Node(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) # verify we have no interactions currently checked this instance verify_no_interactions(spy_node) assert_object(spy_node)\ .is_not_null()\ .is_instanceof(Node)\ .is_not_same(instance) # call first time spy_node.set_process(false) # verify is called one times verify(spy_node).set_process(false) # just double check that verify has no affect to the counter verify(spy_node).set_process(false) # call a scond time spy_node.set_process(false) # verify is called two times verify(spy_node, 2).set_process(false) verify(spy_node, 2).set_process(false) func test_spy_source_with_class_name_by_resource_path() -> void: var instance = auto_free(load('res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd').new()) var m = spy(instance) var head :String = m.get_script().source_code.substr(0, 200) assert_str(head)\ .contains("class_name DoubledMunderwoodPathingWorld")\ .contains("extends 'res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd'") func test_spy_source_with_class_name_by_class() -> void: var m = spy(auto_free(Munderwood_Pathing_World.new())) var head :String = m.get_script().source_code.substr(0, 200) assert_str(head)\ .contains("class_name DoubledMunderwoodPathingWorld")\ .contains("extends 'res://addons/gdUnit4/test/mocker/resources/GD-256/world.gd'") func test_spy_extends_godot_class() -> void: var m = spy(auto_free(World3D.new())) var head :String = m.get_script().source_code.substr(0, 200) assert_str(head)\ .contains("class_name DoubledWorld")\ .contains("extends World3D") func test_spy_on_custom_class(): var instance :AdvancedTestClass = auto_free(AdvancedTestClass.new()) var spy_instance = spy(instance) # verify we have currently no interactions verify_no_interactions(spy_instance) assert_object(spy_instance)\ .is_not_null()\ .is_instanceof(AdvancedTestClass)\ .is_not_same(instance) spy_instance.setup_local_to_scene() verify(spy_instance, 1).setup_local_to_scene() # call first time script func with different arguments spy_instance.get_area("test_a") spy_instance.get_area("test_b") spy_instance.get_area("test_c") # verify is each called only one time for different arguments verify(spy_instance, 1).get_area("test_a") verify(spy_instance, 1).get_area("test_b") verify(spy_instance, 1).get_area("test_c") # an second call with arg "test_c" spy_instance.get_area("test_c") verify(spy_instance, 1).get_area("test_a") verify(spy_instance, 1).get_area("test_b") verify(spy_instance, 2).get_area("test_c") # verify if a not used argument not counted verify(spy_instance, 0).get_area("test_no") # GD-291 https://github.com/MikeSchulze/gdUnit4/issues/291 func test_spy_class_with_custom_formattings() -> void: var resource = load("res://addons/gdUnit4/test/mocker/resources/ClassWithCustomFormattings.gd") var do_spy = spy(auto_free(resource.new("test"))) do_spy.a1("set_name", "", true) verify(do_spy, 1).a1("set_name", "", true) verify_no_more_interactions(do_spy) assert_failure(func(): verify_no_interactions(do_spy))\ .is_failed() \ .has_line(112) func test_spy_copied_class_members(): var instance = auto_free(load("res://addons/gdUnit4/test/mocker/resources/TestPersion.gd").new("user-x", "street", 56616)) assert_that(instance._name).is_equal("user-x") assert_that(instance._value).is_equal(1024) assert_that(instance._address._street).is_equal("street") assert_that(instance._address._code).is_equal(56616) # spy it var spy_instance = spy(instance) reset(spy_instance) # verify members are inital copied assert_that(spy_instance._name).is_equal("user-x") assert_that(spy_instance._value).is_equal(1024) assert_that(spy_instance._address._street).is_equal("street") assert_that(spy_instance._address._code).is_equal(56616) spy_instance._value = 2048 assert_that(instance._value).is_equal(1024) assert_that(spy_instance._value).is_equal(2048) func test_spy_copied_class_members_on_node(): var node :Node = auto_free(Node.new()) # checked a fresh node the name is empty and results into a error when copied at spy # E 0:00:01.518 set_name: Condition "name == """ is true. # C++ Source> scene/main/node.cpp:934 @ set_name() # we set a placeholder instead assert_that(spy(node).name).is_equal("") node.set_name("foo") assert_that(spy(node).name).is_equal("foo") func test_spy_on_inner_class(): var instance :AdvancedTestClass.AtmosphereData = auto_free(AdvancedTestClass.AtmosphereData.new()) var spy_instance :AdvancedTestClass.AtmosphereData = spy(instance) # verify we have currently no interactions verify_no_interactions(spy_instance) assert_object(spy_instance)\ .is_not_null()\ .is_instanceof(AdvancedTestClass.AtmosphereData)\ .is_not_same(instance) spy_instance.set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.2) spy_instance.set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.3) verify(spy_instance, 1).set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.2) verify(spy_instance, 1).set_data(AdvancedTestClass.AtmosphereData.SMOKY, 1.3) func test_spy_on_singleton(): await assert_error(func () -> void: var spy_node_ = spy(Input) assert_object(spy_node_).is_null() await await_idle_frame()).is_push_error("Spy on a Singleton is not allowed! 'Input'") func test_example_verify(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) # verify we have no interactions currently checked this instance verify_no_interactions(spy_node) # call with different arguments spy_node.set_process(false) # 1 times spy_node.set_process(true) # 1 times spy_node.set_process(true) # 2 times # verify how often we called the function with different argument verify(spy_node, 2).set_process(true) # in sum two times with true verify(spy_node, 1).set_process(false)# in sum one time with false # verify total sum by using an argument matcher verify(spy_node, 3).set_process(any_bool()) func test_verify_fail(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) # interact two time spy_node.set_process(true) # 1 times spy_node.set_process(true) # 2 times # verify we interacts two times verify(spy_node, 2).set_process(true) # verify should fail because we interacts two times and not one var expected_error := """ Expecting interaction on: 'set_process(true :bool)' 1 time's But found interactions on: 'set_process(true :bool)' 2 time's""" \ .dedent().trim_prefix("\n") assert_failure(func(): verify(spy_node, 1).set_process(true)) \ .is_failed() \ .has_line(214) \ .has_message(expected_error) func test_verify_func_interaction_wiht_PoolStringArray(): var spy_instance :ClassWithPoolStringArrayFunc = spy(ClassWithPoolStringArrayFunc.new()) spy_instance.set_values(PackedStringArray()) verify(spy_instance).set_values(PackedStringArray()) verify_no_more_interactions(spy_instance) func test_verify_func_interaction_wiht_PackedStringArray_fail(): var spy_instance :ClassWithPoolStringArrayFunc = spy(ClassWithPoolStringArrayFunc.new()) spy_instance.set_values(PackedStringArray()) # try to verify with default array type instead of PackedStringArray type var expected_error := """ Expecting interaction on: 'set_values([] :Array)' 1 time's But found interactions on: 'set_values([] :PackedStringArray)' 1 time's""" \ .dedent().trim_prefix("\n") assert_failure(func(): verify(spy_instance, 1).set_values([])) \ .is_failed() \ .has_line(241) \ .has_message(expected_error) reset(spy_instance) # try again with called two times and different args spy_instance.set_values(PackedStringArray()) spy_instance.set_values(PackedStringArray(["a", "b"])) spy_instance.set_values([1, 2]) expected_error = """ Expecting interaction on: 'set_values([] :Array)' 1 time's But found interactions on: 'set_values([] :PackedStringArray)' 1 time's 'set_values(["a", "b"] :PackedStringArray)' 1 time's 'set_values([1, 2] :Array)' 1 time's""" \ .dedent().trim_prefix("\n") assert_failure(func(): verify(spy_instance, 1).set_values([])) \ .is_failed() \ .has_line(259) \ .has_message(expected_error) func test_reset(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) # call with different arguments spy_node.set_process(false) # 1 times spy_node.set_process(true) # 1 times spy_node.set_process(true) # 2 times verify(spy_node, 2).set_process(true) verify(spy_node, 1).set_process(false) # now reset the spy reset(spy_node) # verify all counters have been reset verify_no_interactions(spy_node) func test_verify_no_interactions(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) # verify we have no interactions checked this mock verify_no_interactions(spy_node) func test_verify_no_interactions_fails(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) # interact spy_node.set_process(false) # 1 times spy_node.set_process(true) # 1 times spy_node.set_process(true) # 2 times var expected_error =""" Expecting no more interactions! But found interactions on: 'set_process(false :bool)' 1 time's 'set_process(true :bool)' 2 time's""" \ .dedent().trim_prefix("\n") # it should fail because we have interactions assert_failure(func(): verify_no_interactions(spy_node)) \ .is_failed() \ .has_line(307) \ .has_message(expected_error) func test_verify_no_more_interactions(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) spy_node.is_ancestor_of(instance) spy_node.set_process(false) spy_node.set_process(true) spy_node.set_process(true) # verify for called functions verify(spy_node, 1).is_ancestor_of(instance) verify(spy_node, 2).set_process(true) verify(spy_node, 1).set_process(false) # There should be no more interactions checked this mock verify_no_more_interactions(spy_node) func test_verify_no_more_interactions_but_has(): var instance :Node = auto_free(Node.new()) var spy_node = spy(instance) spy_node.is_ancestor_of(instance) spy_node.set_process(false) spy_node.set_process(true) spy_node.set_process(true) # now we simulate extra calls that we are not explicit verify spy_node.is_inside_tree() spy_node.is_inside_tree() # a function with default agrs spy_node.find_child("mask") # same function again with custom agrs spy_node.find_child("mask", false, false) # verify 'all' exclusive the 'extra calls' functions verify(spy_node, 1).is_ancestor_of(instance) verify(spy_node, 2).set_process(true) verify(spy_node, 1).set_process(false) # now use 'verify_no_more_interactions' to check we have no more interactions checked this mock # but should fail with a collecion of all not validated interactions var expected_error =""" Expecting no more interactions! But found interactions on: 'is_inside_tree()' 2 time's 'find_child(mask :String, true :bool, true :bool)' 1 time's 'find_child(mask :String, false :bool, false :bool)' 1 time's""" \ .dedent().trim_prefix("\n") assert_failure(func(): verify_no_more_interactions(spy_node)) \ .is_failed() \ .has_line(362) \ .has_message(expected_error) class ClassWithStaticFunctions: static func foo() -> void: pass static func bar(): pass func test_create_spy_static_func_untyped(): var instance = spy(ClassWithStaticFunctions.new()) assert_object(instance).is_not_null() func test_spy_snake_case_named_class_by_resource_path(): var instance_a = load("res://addons/gdUnit4/test/mocker/resources/snake_case.gd").new() var spy_a = spy(instance_a) assert_object(spy_a).is_not_null() spy_a.custom_func() verify(spy_a).custom_func() verify_no_more_interactions(spy_a) var instance_b = load("res://addons/gdUnit4/test/mocker/resources/snake_case_class_name.gd").new() var spy_b = spy(instance_b) assert_object(spy_b).is_not_null() spy_b.custom_func() verify(spy_b).custom_func() verify_no_more_interactions(spy_b) func test_spy_snake_case_named_class_by_class(): var do_spy = spy(snake_case_class_name.new()) assert_object(do_spy).is_not_null() do_spy.custom_func() verify(do_spy).custom_func() verify_no_more_interactions(do_spy) # try checked Godot class var spy_tcp_server = spy(TCPServer.new()) assert_object(spy_tcp_server).is_not_null() spy_tcp_server.is_listening() spy_tcp_server.is_connection_available() verify(spy_tcp_server).is_listening() verify(spy_tcp_server).is_connection_available() verify_no_more_interactions(spy_tcp_server) const Issue = preload("res://addons/gdUnit4/test/resources/issues/gd-166/issue.gd") const Type = preload("res://addons/gdUnit4/test/resources/issues/gd-166/types.gd") func test_spy_preload_class_GD_166() -> void: var instance = auto_free(Issue.new()) var spy_instance = spy(instance) spy_instance.type = Type.FOO verify(spy_instance, 1)._set_type_name(Type.FOO) assert_int(spy_instance.type).is_equal(Type.FOO) assert_str(spy_instance.type_name).is_equal("FOO") var _test_signal_args := Array() func _emit_ready(a, b, c = null): _test_signal_args = [a, b, c] # https://github.com/MikeSchulze/gdUnit4/issues/38 func test_spy_Node_use_real_func_vararg(): # setup var instance := Node.new() instance.connect("ready", _emit_ready) assert_that(_test_signal_args).is_empty() var spy_node = spy(auto_free(instance)) assert_that(spy_node).is_not_null() # test emit it spy_node.emit_signal("ready", "aa", "bb", "cc") # verify is emitted verify(spy_node).emit_signal("ready", "aa", "bb", "cc") await get_tree().process_frame assert_that(_test_signal_args).is_equal(["aa", "bb", "cc"]) # test emit it spy_node.emit_signal("ready", "aa", "xxx") # verify is emitted verify(spy_node).emit_signal("ready", "aa", "xxx") await get_tree().process_frame assert_that(_test_signal_args).is_equal(["aa", "xxx", null]) class ClassWithSignal: signal test_signal_a signal test_signal_b func foo(arg :int) -> void: if arg == 0: emit_signal("test_signal_a", "aa") else: emit_signal("test_signal_b", "bb", true) func bar(arg :int) -> bool: if arg == 0: emit_signal("test_signal_a", "aa") else: emit_signal("test_signal_b", "bb", true) return true # https://github.com/MikeSchulze/gdUnit4/issues/14 func _test_spy_verify_emit_signal(): var spy_instance = spy(ClassWithSignal.new()) assert_that(spy_instance).is_not_null() spy_instance.foo(0) verify(spy_instance, 1).emit_signal("test_signal_a", "aa") verify(spy_instance, 0).emit_signal("test_signal_b", "bb", true) reset(spy_instance) spy_instance.foo(1) verify(spy_instance, 0).emit_signal("test_signal_a", "aa") verify(spy_instance, 1).emit_signal("test_signal_b", "bb", true) reset(spy_instance) spy_instance.bar(0) verify(spy_instance, 1).emit_signal("test_signal_a", "aa") verify(spy_instance, 0).emit_signal("test_signal_b", "bb", true) reset(spy_instance) spy_instance.bar(1) verify(spy_instance, 0).emit_signal("test_signal_a", "aa") verify(spy_instance, 1).emit_signal("test_signal_b", "bb", true) func test_spy_func_with_default_build_in_type(): var spy_instance :ClassWithDefaultBuildIntTypes = spy(ClassWithDefaultBuildIntTypes.new()) assert_object(spy_instance).is_not_null() # call with default arg spy_instance.foo("abc") spy_instance.bar("def") verify(spy_instance).foo("abc", Color.RED) verify(spy_instance).bar("def", Vector3.FORWARD, AABB()) verify_no_more_interactions(spy_instance) # call with custom args spy_instance.foo("abc", Color.BLUE) spy_instance.bar("def", Vector3.DOWN, AABB(Vector3.ONE, Vector3.ZERO)) verify(spy_instance).foo("abc", Color.BLUE) verify(spy_instance).bar("def", Vector3.DOWN, AABB(Vector3.ONE, Vector3.ZERO)) verify_no_more_interactions(spy_instance) func test_spy_scene_by_resource_path(): var spy_scene = spy("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") assert_object(spy_scene)\ .is_not_null()\ .is_not_instanceof(PackedScene)\ .is_instanceof(Control) assert_str(spy_scene.get_script().resource_name).is_equal("SpyTestScene.gd") # check is spyed scene registered for auto freeing assert_bool(GdUnitMemoryObserver.is_marked_auto_free(spy_scene)).is_true() func test_spy_on_PackedScene(): var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") var original_script = resource.get_script() assert_object(resource).is_instanceof(PackedScene) var spy_scene = spy(resource) assert_object(spy_scene)\ .is_not_null()\ .is_not_instanceof(PackedScene)\ .is_not_same(resource) assert_object(spy_scene.get_script())\ .is_not_null()\ .is_instanceof(GDScript)\ .is_not_same(original_script) assert_str(spy_scene.get_script().resource_name).is_equal("SpyTestScene.gd") # check is spyed scene registered for auto freeing assert_bool(GdUnitMemoryObserver.is_marked_auto_free(spy_scene)).is_true() func test_spy_scene_by_instance(): var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") var instance :Control = resource.instantiate() var original_script = instance.get_script() var spy_scene = spy(instance) assert_object(spy_scene)\ .is_not_null()\ .is_same(instance)\ .is_instanceof(Control) assert_object(spy_scene.get_script())\ .is_not_null()\ .is_instanceof(GDScript)\ .is_not_same(original_script) assert_str(spy_scene.get_script().resource_name).is_equal("SpyTestScene.gd") # check is mocked scene registered for auto freeing assert_bool(GdUnitMemoryObserver.is_marked_auto_free(spy_scene)).is_true() func test_spy_scene_by_path_fail_has_no_script_attached(): var resource := load("res://addons/gdUnit4/test/mocker/resources/scenes/TestSceneWithoutScript.tscn") var instance :Control = auto_free(resource.instantiate()) # has to fail and return null var spy_scene = spy(instance) assert_object(spy_scene).is_null() func test_spy_scene_initalize(): var spy_scene = spy("res://addons/gdUnit4/test/mocker/resources/scenes/TestScene.tscn") assert_object(spy_scene).is_not_null() # Add as child to a scene tree to trigger _ready to initalize all variables add_child(spy_scene) # ensure _ready is recoreded and onyl once called verify(spy_scene, 1)._ready() verify(spy_scene, 1).only_one_time_call() assert_object(spy_scene._box1).is_not_null() assert_object(spy_scene._box2).is_not_null() assert_object(spy_scene._box3).is_not_null() # check signals are connected assert_bool(spy_scene.is_connected("panel_color_change",Callable(spy_scene,"_on_panel_color_changed"))) # check exports assert_str(spy_scene._initial_color.to_html()).is_equal(Color.RED.to_html()) class CustomNode extends Node: func _ready(): # we call this function to verify the _ready is only once called # this is need to verify `add_child` is calling the original implementation only once only_one_time_call() func only_one_time_call() -> void: pass func test_spy_ready_called_once(): var spy_node = spy(auto_free(CustomNode.new())) # Add as child to a scene tree to trigger _ready to initalize all variables add_child(spy_node) # ensure _ready is recoreded and onyl once called verify(spy_node, 1)._ready() verify(spy_node, 1).only_one_time_call()