Link Search Menu Expand Document

Mocking / Mocks

Mocking is current only supported for GdScripts!

Definition

A mocked object is a dummy implementation for an class in which you define the output of certain function calls. Mocked objects are configured to perform a certain behavior during a test and tracks all function calls and their parameters to the mocked object.

This kind of testing is sometimes called behavior testing. Behavior testing does not check the result of a function call, but it checks that a function is called with the right parameters.

For detailed info about mocks you have to read


Here an small example to mock the class TestClass.

  •     class_name TestClass
        extends Node
    
            func message() -> String:
                return "a message"
    
  •     func test_mock():
            # create a mock for class 'TestClass' by mock mode `RETURN_DEFAULTS` (default)
            var mock := mock(TestClass) as TestClass
    
            # inital the mock will return a default value, for string means an empty string
            assert_str(mock.message()).is_empty()
    
            # new we override the return value for `message()` to return 'custom message'
            do_return("custom message").on(mock).message()
    
            # the next call of `message()` will now return 'custom message'
            assert_str(mock.message()).is_equal("custom message")
    
  •     func test_mock():
            # create a mock for class 'TestClass' using mode `CALL_REAL_FUNC`
            var mock := mock(TestClass, CALL_REAL_FUNC) as TestClass
    
            # inital the mock will return a original value (calles the real implementation)
            assert_str(mock.message()).is_equal("a message")
    
            # new we override the return value for `message()` to return 'custom message'
            do_return("custom message").on(mock).message()
    
            # the next call of `message()` will now return 'custom message'
            assert_str(mock.message()).is_equal("custom message")
    

How to use a Mock

To mock a class you only need to use mock(<class_name>) or mock(<resource_path>) to create a mocked object instance by given class name or path. A mocked instance is marked for auto free, you don’t need to free it manually.

To enable creation a mock by class name you have to defined the class_name in your class otherwise the class must be mock by resource path.

    # example class 
    class_name TestClass
    extends Node
        ...
    # create a mocked instance of by class 'TestClass'
    var mock := mock(TestClass)
    # or create by using the full resource path if no `class_name` defined
    var mock := mock("res://project_name/src/TestClass.gd")

You can also mock inner classes by using mock(<class_name>) by some preconditions.

How and Why we overwrite functions

With a mock you can override a specific function to return custom values. This allows you to simulate a function and return an expected value without calling the actual implementation.

To override a function on your mocked class use do_return(<value>) to specify the return value.

Syntax

do_return(<value>) .on(<mock>) .<function([args])>)

First you have to define the return value, then the mock and finally the function you want to override.

    var node := mock(Node) as Node
    do_return("NodeX").on(node).get_name()

Example

    # create a mock from class `Node`
    var mocked_node := mock(Node) as Node

    # is return 0 by default
    mocked_node.get_child_count()
    # override function `get_child_count` to return 10
    do_return(10).on(mocked_node).get_child_count()
    # next call of `get_child_count` will now return 10
    mocked_node.get_child_count()
    
    # is return 'null' by default
    var node = mocked_node.get_child(0)
    assert_object(node).is_null()
    
    # override function `get_child` to return a mocked 'Camera' for child index 0
    do_return(mock(Camera)).on(mocked_node).get_child(0)
    # and a mocked 'Area' for child index 1
    do_return(mock(Area)).on(mocked_node).get_child(1)
    
    # it returns now on indec 0 the Camera node
    var node0 = mocked_node.get_child(0)
    assert_object(node0).is_instanceof(Camera)
    # and on index 1 the Area node
    var node1 = mocked_node.get_child(1)
    assert_object(node1).is_instanceof(Area)

Verification of function calls

A mock keeps track of all the function calls and their arguments. Use verify() on the mock to verify that the specified conditions are met. This way you can check if a certain function is called and how often it was called.

Function Description
verify Verifies certain behavior happened at least once or exact number of times
verify_no_interactions Verifies no interactions is happen on this mock
verify_no_more_interactions Verifies the given mock has any unverified interaction
reset Resets the saved function call counters on a mock

verify_no_interactions

Verifies no interactions is happen on this mock.

    verify_no_interactions(<mock>)
    var mocked_node := mock(Node) as Node
    
    # test we have initial no interactions on this mock
    verify_no_interactions(mocked_node)

    # interact by calling `get_name()`
    mocked_node.get_name()

    # now this verification will fail because we have interacted on this mock
    verify_no_interactions(mocked_node)

verify_no_more_interactions

Checks whether the specified mock has no further interaction.

If the mock has recorded more interactions than you verified with verify(), an error is reported.

    verify_no_more_interactions(<mock>)
    var mocked_node := mock(Node) as Node
    
    # interact on two functions 
    mocked_node.is_a_parent_of(null)
    mocked_node.set_process(false)
    # verify if interacts
    verify(mocked_node).is_a_parent_of(null)
    verify(mocked_node).set_process(false)
    # finally we want to check no more interactions on this mock was happen
    verify_no_more_interactions(mocked_node)

    # simmulate a unexpected interaction on `set_process`
    mocked_node.set_process(false)
    # no the verify will fail because we have an interacted on `set_process(false)` where we not expected
    verify_no_more_interactions(mocked_node)

verify

Verifies certain behavior happened at least once or exact number of times

    verify(<mock>, <times>).function(<args>)
    var mocked_node :Node = mock(Node)
    
    # verify we have no interactions currently on this instance
    verify_no_interactions(mocked_node)
    
    # call with different arguments
    mocked_node.set_process(false) # 1 times
    mocked_node.set_process(true) # 1 times
    mocked_node.set_process(true) # 2 times
    
    # verify how often we called the function with different argument 
    verify(mocked_node, 1).set_process(false)# in sum one time with false
    verify(mocked_node, 2).set_process(true) # in sum two times with true

    # verify will fail because we expect the function `set_process(true)` is called 3 times but was called 2 times
    verify(mocked_node, 3).set_process(true)

reset

Resets the recorded function interactions of given mock.

Sometimes we want to reuse an already created mock for different test scenarios and have to reset the recorded interactions.

    reset(<mock>)
    var mocked_node :Node = mock(Node)
    
    # first testing interact on two functions 
    mocked_node.is_a_parent_of(null)
    mocked_node.set_process(false)
    # verify if interacts,at this point two interactions are recorded
    verify(mocked_node).is_a_parent_of(null)
    verify(mocked_node).set_process(false)


    # now we want to test a other scenario and we need to reset the current recorded interactions
    reset(mocked_node)
    # we verify the previously recorded interactions have been removed
    verify_no_more_interactions(mocked_node)

    # continue testing ..
    mocked_node.set_process(true)
    verify(mocked_node).set_process(true)
    verify_no_more_interactions(mocked_node)

Mock Working Modes

When creating a mock, you can specify the working mode that defines the return value handling of function calls for a mock.

  • RETURN_DEFAULTS (default)
  • CALL_REAL_FUNC
  • RETURN_DEEP_STUB (not yet implemented!)
  • If RETURN_DEFAULTS is used, all unoverridden function calls return default values for a mocked class.

        var mock := mock(TestClass) as TestClass
    
        # returns a default value (for String an empty value)
        assert_str(mock.message()).is_equal("")
    
    
  • If CALL_REAL_FUNC is used, all unoverridden function calls return the value provided by the real implementation for a mocked class. Helpful when you only want to mock partial functions of a class.

        # build a mock with mode CALL_REAL_FUNC
        var mock := mock(TestClass, CALL_REAL_FUNC) as TestClass
    
        # returns the real implementation value
        assert_str(mock.message()).is_equal("a message")
    
        # set a the return value to 'custom message' for the function message()
        do_return("custom message").on(mock).message()
    
        # now the function message will return 'custom message'
        assert_str(mock.message()).is_equal("custom message")
    
  • WORK IN PROGRESS – NOT SUPPORTED YET!!!

    If RETURN_DEEP_STUB is used, all unoverridden function calls return the value provided by the real implementation for a mocked class. Use to return a default value for build-in types or a fully mocked value for Object types.

        # build a mock with mode RETURN_DEEP_STUB
        var mock := mock(TestClass, RETURN_DEEP_STUB) as TestClass
    
        # returns a default value 
        assert_str(mock.message()).is_equal("")
    
        # returns a mocked Path value
        assert_object(mock.path()).is_not_null()
    

Default Values

Unconfigured function calls do return a default value for mock working mode RETURN_DEFAULTS

Type default value
TYPE_NIL null
TYPE_BOOL false
TYPE_INT 0
TYPE_REAL 0.0
TYPE_STRING ””
TYPE_VECTOR2 Vector2.ZERO
TYPE_RECT2 Rect2()
TYPE_VECTOR3 Vector3.ZERO
TYPE_TRANSFORM2D Transform2D()
TYPE_PLANE Plane()
TYPE_QUAT Quat()
TYPE_AABB AABB()
TYPE_BASIS Basis()
TYPE_TRANSFORM Transform()
TYPE_COLOR Color()
TYPE_NODE_PATH NodePath()
TYPE_RID RID()
TYPE_OBJECT null
TYPE_DICTIONARY Dictionary()
TYPE_ARRAY Array()
TYPE_RAW_ARRAY PoolByteArray()
TYPE_INT_ARRAY PoolIntArray()
TYPE_REAL_ARRAY PoolRealArray()
TYPE_STRING_ARRAY PoolStringArray()
TYPE_VECTOR2_ARRAY PoolVector2Array()
TYPE_VECTOR3_ARRAY PoolVector3Array()
TYPE_COLOR_ARRAY PoolColorArray()

Argument Matchers and mocks

To simplify the verification of function calls, you can use an argument matcher. This allows you to verify function calls by a specific type or class argument.

    var mocked_node :Node = mock(Node)
    
    # call with different arguments
    mocked_node.set_process(false) # 1 times
    mocked_node.set_process(true) # 1 times
    mocked_node.set_process(true) # 2 times
    
    # verify how often we called the function with a boolean argument
    verify(mocked_node, 3).set_process(any_bool())

For more details please show at Argument Matchers


Copyright © 2021-2022 Mike Schulze. Distributed by an MIT license.