IMPLEMENT_SIMPLE_AUTOMATION_TEST(GeneratesOnKilledEvent, "GeneratesOnKilledEvent", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)
bool GeneratesOnKilledEvent::RunTest(const FString& Parameters)
{
AHealthComponentTest* HealthComponentTest = NewObject<AHealthComponentTest>();
UHealthComponent* HealthComponent = NewObject<UHealthComponent>(HealthComponentTest);
HealthComponent->OnKilledEvent.AddDynamic(HealthComponentTest, &AHealthComponentTest::OnKilledEventHandler);
HealthComponent->OnKilledEvent.Broadcast();
TestTrue(TEXT("OnKilledEventHandlerCalled should be called when health reaches 0"), HealthComponentTest->OnKilledEventHandlerCalled);
return true;
}
The GeneratesOnKilledEvent::RunTest test fails at the assertion: TestTrue. The HealthComponentTest->OnKilledEventHandlerCalled is false, but it should be true. The event works in the editor: I successfully bind a UFUNCTION in a real character actor, trigger it and the delegate successfully calls my event handler.
I also tried to create the test in the world and set the AHealthComponentTest actor as the owner of the HealthComponent:
bool GeneratesOnKilledEvent::RunTest(const FString& Parameters)
{
auto World = FAutomationEditorCommonUtils::CreateNewMap();
AHealthComponentTest* HealthComponentTest = World->SpawnActor<AHealthComponentTest>();
UHealthComponent* HealthComponent = NewObject<UHealthComponent>(HealthComponentTest);
HealthComponent->OnKilledEvent.AddDynamic(HealthComponentTest, &AHealthComponentTest::OnKilledEventHandler);
HealthComponent->OnKilledEvent.Broadcast();
TestTrue(TEXT("OnKilledEventHandlerCalled should be called when health reaches 0"), HealthComponentTest->OnKilledEventHandlerCalled);
return true;
}
Ok. I managed to fix this. There is no solution for this on the Internet, so I’ll post it here. The way I fixed it is: I downloaded UE4 debugging symbols and started debugging from: HealthComponent->OnKilledEvent.Broadcast(). I first confirmed that in the Editor when I press play and hit a character, that my event handler is triggered immediately once the Broadcast() function is called. I then added a breakpoint on the call to Broadcast() event in the test and started debugging from there to see how it calls all these event handlers. 9 rings down the UE hell in the AActor::ProcessEvent function there’s this:
It checks if there is a world, if all actors initialized and if bAllowScriptExecution is true. When I was debugging, I did create the world, but the AreActorsInitialized returned false and bAllowScriptExecution was false, too. So it skipped the Super::ProcessEvent call, and didn’t call the event handlers respectively. I continued the execution, added breakpoint on the top if line, ran the tests again and changed bAllowScriptExecution from false to true in the debugger to force it to enter the if block. I continued the execution and the test passed! Now, how do I make it actually work then? Well, I decided to see what does AreActorsInitialized do: return bActorsInitialized && PersistentLevel && PersistentLevel->Actors.Num();. And the bActorsInitialized are set to true in UWorld::InitializeActorsForPlay. So what I did in the end to make it work is call World->InitializeActorsForPlay({}) in the test after I create the world:
bool GeneratesOnKilledEvent::RunTest(const FString& Parameters)
{
auto World = FAutomationEditorCommonUtils::CreateNewMap();
World->InitializeActorsForPlay({});
AHealthComponentTest* HealthComponentTest = World->SpawnActor<AHealthComponentTest>();
UHealthComponent* HealthComponent = NewObject<UHealthComponent>(HealthComponentTest);
auto Damage = HealthComponent->GetMaxHealth();
HealthComponent->OnKilledEvent.AddDynamic(HealthComponentTest, &AHealthComponentTest::OnKilledEventHandler);
HealthComponent->OnKilledEvent.Broadcast();
TestTrue(TEXT("OnKilledEventHandlerCalled should be called when health reaches 0"), HealthComponentTest->OnKilledEventHandlerCalled);
return true;
}
The curly brackets {} in the InitializeActorsForPlay({}) are for the empty FURL argument. It’s safe to do: UE4 itself does it in the AudioMixerCommandlet.cpp.
So, today I had the same issue and went to the same place in engine code (AActor::ProcessEvent) and I found out that the solution was a lot easier than I expected. All you have to do is add CallInEditor to the UFUNCTION specifier