DEV Community

Cover image for Unit testing private functionality in Unreal Engine C++ classes
Göran Syberg Falguera for GOALS Engineering

Posted on

Unit testing private functionality in Unreal Engine C++ classes

Unreal Engine has a pretty extensible suit of automation features. For instance, we run unit tests in our continuous integration system upon merge of new code.

For these unit tests we utilize UE's IMPLEMENT_SIMPLE_AUTOMATION_TEST or potentially IMPLEMENT_COMPLEX_AUTOMATION_TEST. Here is a minimal test from Unreal's documentation:

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FPlaceholderTest, "TestGroup.TestSubgroup.Placeholder Test", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

bool FPlaceholderTest::RunTest(const FString& Parameters)
{
    // Make the test pass by returning true, or fail by returning false.
    return true;
}
Enter fullscreen mode Exit fullscreen mode

This is all well and good but pretty soon after implementing your first test you realize that you will want to reach private functionality in the class that you are testing.

After pondering different more or less ugly solutions I talked to my colleague Tomas Hübner and he showed me this old trick that does not make this pretty but at least not terrible. And after pre-compiler is done the class we are testing is actually unaffected.

We do not want to make this private functionality public just for testing and we do not want to pollute the tested class with for instance a friend class that the original class should know nothing about. So, here is the trick:

We add an empty macro UNIT_TEST_FRIEND to the class we want to test:

#pragma once

#include "CoreMinimal.h"

#ifndef UNIT_TEST_FRIEND
#define UNIT_TEST_FRIEND
#endif

class FMyTestedClass : public FCoolUnrealClass
{
public:
    FMyTestedClass();

private:
    float PrivateFloat;
    void PrivateMethod();

    UNIT_TEST_FRIEND;
};
Enter fullscreen mode Exit fullscreen mode

And then in the test class, we make sure to define the UNIT_TEST_FRIEND macro. The test class from above then becomes:

#include "Misc/AutomationTest.h"

#if WITH_AUTOMATION_TESTS

#ifdef UNIT_TEST_FRIEND
#undef UNIT_TEST_FRIEND
#endif
#define UNIT_TEST_FRIEND friend class FPlaceholderTest

#include "MyTestedClass.h"

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FPlaceholderTest, "TestGroup.TestSubgroup.Placeholder Test", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

bool FPlaceholderTest::RunTest(const FString& Parameters)
{
    FMyTestedClass TestedClass;

    // Using private member here
    float MyValue = TestedClass.PrivateFloat;

    // Calling private function here
    TestedClass.PrivateMethod();

    return true;
}
Enter fullscreen mode Exit fullscreen mode

Note how we include MyTestedClass.h after defining the macro! Otherwise the UNIT_TEST_FRIEND will be empty when we parse the MyTestedClass.h header.

Tada!

Top comments (1)

Collapse
 
tomashu profile image
Tomas Hübner • Edited

It is quite common that each test file contains multiple test cases, and with the UHT macros that would result in several classes needing to be friends with the test subject.

While it would be totally possible to put several class friends in the macro, you could instead introduce a new wrapper or decorator class as the friend class that all the generated test classes can use. That would also allow for the ickyness of the macro-business to be put in a separate file, out of sight of those more faint of heart.