UE Automated Testing 101: Verify HP Changes in Blueprint

Created: 2026-07-25Last updated: 2026-09-05

Turn your usual manual check into a test you can repeat endlessly. Using UE's Functional Test, verify in Blueprint that 20 damage takes HP from 100 to 80. Illustrates the mechanism, node wiring, and deliberately failing the test to confirm it works.

Every time you fix damage calculation, you play the game, attack an enemy, and check how HP drops. Once critical hits and defense logic pile up, re-verifying attacks that used to work becomes a chore.

Automated testing makes those checks repeatable with the same steps. This article uses UE's Functional Test to verify in Blueprint that "dealing 20 damage takes HP from 100 to 80".

A developer checking automated test results

What You'll Learn

  • How your usual visual check differs from an automated test
  • Functional Test's role and how to build a first test
  • Wiring nodes that compare an enemy's HP and judge pass or fail
  • Deliberately introducing a defect to confirm the test finds it

Sponsored

Keep your usual check as a test

Normally you display HP with Print String and confirm "it says 80, so we are fine". An automated test hands the part where you look at the value and judge over to Blueprint.

Comparing a manual check that reads HP with an automated test that keeps the steps and judgment for re-running

For example, build a test that runs these three in order.

  1. Prepare an enemy with 100 HP.
  2. Deal it 20 damage.
  3. Record success if HP is 80 and failure otherwise.

Keeping that lets you add critical-hit logic later and re-confirm that "a normal attack takes off 20" simply by running the test. A one-time check becomes usable for the next change too.

What in your game do you test?

Start with things whose conditions and answers you can state clearly.

Three test subjects: damage calculation, a puzzle match check, and saving a chest's state
  • RPG : taking 20 damage drops HP from 100 to 80.
  • Puzzle : three identical gems in a row clear, two do not.
  • Adventure : opening a chest and saving leaves it open after loading.

You do not have to test everything at once. Picking one thing you check repeatedly, or a condition that once had a bug, makes the value of building it tangible.

A Functional Test is an Actor for verifying behavior

An Actor is the base kind of thing you can place in a level, such as an enemy or a light. A level is where you arrange them to build a game scene.

A Functional Test is an Actor with features for starting a test and recording results. We build a Blueprint from it with the steps "prepare an enemy, deal damage, and check HP".

It is built separately from the enemy Blueprint, so start by separating the two roles.

The test Blueprint dealing 20 damage to the enemy and comparing the HP the enemy updated against an expected 80
  • BP_Enemy : reduces its own HP when damaged. The logic used in the actual game.
  • BP_Test_Damage : calls the enemy's logic and verifies the resulting HP. This is the test.

What matters is that the test calls the real game logic . Overwriting HP to 80 on the test side would pass even with the damage logic broken. Reducing HP is the enemy's job; the test reads the result.

Functional Tests can also be extended in C++, but the test we build here works in Blueprint alone.

First build a test that starts and finishes

Rather than building the HP check immediately, get to "run the test and see success" first. Getting that through first lets you separate test setup from damage logic when something later fails.

Creating a test level and placing a Blueprint with Functional Test as the parent

1. Prepare a test level

Search for Functional Testing Editor in "Edit → Plugins" and enable it. Restart the editor if prompted.

Next create a small level with just a floor and a light and save it as L_Test_Damage . That gives a place to test under the same conditions without interference from other enemies or game progression.

2. Create and place the test Blueprint

Create a "Blueprint Class" in the Content Browser and search for Functional Test under "All Classes". Choose it as the parent class and name it BP_Test_Damage . A parent class is the foundation deciding what features a new Blueprint inherits. Choosing Functional Test here gives you the test start and finish features.

Place one of the created Blueprint in the test level and save the level. Preparation includes placing it in the level where it runs , not just creating the asset.

3. Connect start to finish

Open BP_Test_Damage and place Event Start Test and Finish Test in the event graph. The event graph is the editing screen where you arrange nodes representing logic and connect them with wires.

A node's connection points are called pins . Connecting the white arrow-shaped exec pins decides "which logic runs after which". Wire Event Start Test 's exec output into Finish Test 's exec input and set Test Result to Succeeded .

Event Start Test → Finish Test (Test Result = Succeeded)
  • Event Start Test : the entry point when the test runs.
  • Finish Test : ends the test and reports the result. Succeeded is pass and Failed is fail.

Verification logic starts from Event Start Test rather than the usual Event BeginPlay . We insert the HP check between those two nodes next.

Note: finding tests that never finish

In the placed test Actor's "Details", set Time Limit to 5.0 seconds and Times Up Result to Failed . Even if you forget to wire the finish logic, it reports failure rather than waiting forever.

Run it in Session Frontend

Session Frontend is the screen for selecting, running, and reviewing tests. Compile the Blueprint, save the level, and open it.

Selecting a test in the Automation tab, running with Start Tests, and reviewing the result
  1. Open "Tools → Test Automation". On some versions, open the "Automation" tab from "Session Frontend".
  2. Choose the currently open editor session as the run target. A session is the unit of an editor or game running tests. Specify the editor you are working in.
  3. Find your test level or the placed test's name under Project in the list and check it.
  4. Press "Start Tests".

The test completing and showing success means you are ready. We only connected start to finish so far; the HP check comes next.

If it does not appear in the list, confirm you placed the test Actor and saved the level. The plugin, the selected session, and search filters are other things to check. The diagram is a schematic; display names and hierarchy vary by UE version.

Sponsored

Assert is the "this is what the result should be" check

To check whether HP became 80, use a node called Assert . It compares "the actual result" with "what the result should be".

That "what the result should be" is the expected value . Here it is 80 , 100 minus 20.

The expected value staying at 80, with an actual HP of 80 passing and 100 failing

We build HP as a Float , the numeric type handling decimals. Match the value's type by using Assert Equal (Float) and pass these two.

  • Actual : the HP read from the enemy after taking damage.
  • Expected : the expected value, 80.0 in this example.

There is also Assert Equal (Integer) for whole numbers such as score and Assert True for conditions such as whether a door opened. Being able to use this one Float comparison is enough for now.

Separate "comparing" from "finishing"

A Return Value is the value a node returns as its result. For an Assert, it returns true when the comparison passed and false when it did not. That yes-or-no type is a Boolean (Bool) .

Branch is the node that splits which logic runs next based on whether a condition is true or false. Pass the Assert's return value into Branch's Condition and continue to Finish Test as success on true and failure on false.

The flow is verify with Assert and end with Finish Test . The exercise confirms this wiring with diagrams too.

Hands-On: verify 20 damage takes HP from 100 to 80

Now we connect the enemy and the test for real. When finished, dealing damage to the enemy shows success when HP is 80. Removing the HP-reducing logic turns the same test into a failure.

The exercise's result: failing when the damage logic is defective and passing once it is fixed

1. Prepare an enemy whose HP drops on damage

First, BP_Enemy , the side being verified. If you already have the enemy from the damage article, use its equivalent variable and logic. Building it fresh, use Actor as the parent class and add this variable.

VariableTypeDefault
CurrentHealthFloat100.0

Enable Can Be Damaged in "Class Defaults". We run this standalone and add no logic changing HP elsewhere, such as AI or regeneration.

This enemy works with just the HP variable and damage logic. Without visual components it does not appear on screen, but the spawned enemy's HP is readable from the test.

Build these connections in the event graph. The damage Event AnyDamage receives is subtracted from current HP and written back. The connection diagrams are arranged for readability, so match node and pin names in the editor.

Subtracting Event AnyDamage's Damage from CurrentHealth and writing it back with Set CurrentHealth
  1. Connect Event AnyDamage 's and Set CurrentHealth 's exec pins.
  2. Pass CurrentHealth into the Float subtraction node's A and Damage into B . The order is current HP minus damage .
  3. Connect the subtraction's result into Set CurrentHealth 's value.

Apply Damage , which the test uses later, is the node that reports damage to the enemy. It does not rewrite the HP variable automatically, which is why the enemy needs this logic.

2. Spawn the enemy from the test and deal damage

Next, BP_Test_Damage . Add these variables.

VariableTypeValue / purpose
TestDamageFloat20.0 . The damage to deal
ExpectedHealthFloat80.0 . The expected HP
enemyBP_Enemy Object ReferenceStores the spawned enemy

enemy is the variable for specifying the enemy you just created later . You deal damage to the enemy stored there and read HP from that same enemy.

Disconnect the minimal wiring to Finish Test and build the enemy spawn first.

Spawning the enemy from test start, saving the reference, continuing to B when valid, and failing when the spawn fails
  1. Spawn Actor from Class creates one Actor from a specified Blueprint. Set Class to BP_Enemy . Spawn Transform specifies position, rotation, and scale at spawn; use a position not overlapping the floor or other Actors.
  2. Pass Return Value into Set enemy to store the spawned enemy.
  3. Use the Is Valid with an exec pin to confirm enemy points at an actual enemy. A failed spawn goes down Is Not Valid , so set Finish Test to Failed there with Message reading "Could not spawn the enemy".

Get enemy in the diagram retrieves the stored enemy. Drag the variable into the graph and choose "Get". Read TestDamage and ExpectedHealth with "Get" the same way.

Once the enemy spawns, continue from "B" in the diagram. "B" and "A" mark connections across diagrams; they are not nodes to add.

Registering cleanup and running Apply Damage from B, then judging HP past A, passing the stored enemy into both
  1. Pass enemy into Register Auto Destroy Actor 's Actor . That is the setting for cleaning up this enemy automatically when the test ends. Leave Target (what this feature is called on) as Self , which here is the BP_Test_Damage holding the logic.
  2. Pass enemy into Apply Damage 's Damaged Actor and TestDamage into Base Damage . The attacker-related inputs are unused in this example and can stay unconnected.
  3. Connect past "A" into the HP check in the next section.

We spawn a new enemy each time so verification always starts from 100 HP . Reusing an enemy left at 80 from a previous test would land at 60 and fail even with correct damage logic.

3. Compare HP and finish the test

Connect Assert Equal (Float) after Apply Damage . Dragging from Get enemy 's output and searching Get CurrentHealth places the node reading that enemy's HP. Pass the read value into Actual .

Continuing from the previous diagram's A into the Assert and choosing success or failure from a Branch on the comparison result
PinValue to pass
TargetSelf (this test itself)
Actualenemy 's CurrentHealth
ExpectedExpectedHealth ( 80.0 )
What"HP after applying damage"
Tolerance0.01

What is a name making clear what was verified. It appears in failure records, so writing "HP after applying damage" rather than "Test 1" makes investigation easier.

Tolerance sets how much of the small error inherent in decimal math to allow. We use 0.01 here. HP of 80 versus 100 exceeds that range by far and fails.

Finally, wire the Assert's exec output into a Branch and its Return Value into Condition . True goes to Finish Test → Succeeded and False to Finish Test → Failed .

The diagram separates the lines advancing execution from those passing values. Get CurrentHealth only reads a value and has no exec pin. Connect the line passing the HP value into the Assert.

4. Deliberately fail it, then restore it

Compile, save, run, and confirm it passes. Next, test whether it catches HP not dropping .

  1. Temporarily change BP_Enemy 's subtraction from CurrentHealth − Damage to CurrentHealth − 0 .
  2. Compile, save, and run the same test.
  3. Open the failure's details and look for "HP after applying damage". The expected value should be 80 and the actual 100.
  4. Restore the subtraction to CurrentHealth − Damage and run again. Returning to success completes the confirmation.

We do not change the expected 80 throughout. Keeping the baseline of "it should be 80" is exactly what catches HP failing to drop.

Once that works, change TestDamage to 35.0 . With the expected value still 80 it fails, and setting ExpectedHealth to 65.0 passes. Pairing the value dealt with the expected result lets you build tests the same way for other attacks. Return to this exercise's 20 and 80 afterwards.

Two points matter.

  • Start from the same conditions every time : do not let previous HP or leftover enemies influence the result.
  • Verify failure as well as success : if introducing a defect still passes, it is not connected to the logic or value you meant to verify.
Sponsored

When it does not work

First separate whether the test could not run from whether it ran and gave an unexpected result.

SymptomWhere to check
The test is not in the listPlacing the test Actor and saving the level, the plugin, the session, search filters
It never finishes or times outWhether both success and failure paths reach Finish Test
The enemy will not spawnClass and Spawn Transform , and whether the spawn position overlaps something
HP stays at 100Whether Can Be Damaged is on, and whether the enemy subtracts and writes back with Set CurrentHealth
It passes even without reducing HPThe Assert's exec line, the value passed into Actual , the connection from Return Value to the Branch

To follow values, breaking at Event AnyDamage with the Blueprint Debugger shows whether the damage arrived and which HP was overwritten.

Bonus: good to know up front

After fixing a defect, keep that condition

Having fixed a bug such as "taking damage while invulnerable", a test that attacks an invulnerable enemy and confirms HP is unchanged earns its place. If the same defect returns through a later change, it becomes the clue that finds it.

Rather than building many at once, adding one test whenever a condition you check repeatedly appears is easier to sustain.

Verify at the moment the result exists

Our enemy updates HP on the spot when damaged. That lets us compare HP right after Apply Damage .

To check whether a door finished opening, though, right after calling the open logic is too early. Wait for the moment the result exists, such as animation completion, before verifying. Keep "the logic was called" separate from "the motion finished".

Keep both numeric verification and hands-on play

This test calls Apply Damage directly, so it does not go through a projectile hitting an enemy. Even with correct HP math, how easy an attack is to land and how well damage effects read are confirmed by playing.

Deciding when to re-run tests — after changing damage logic, before packaging — reduces forgetting to use them.

Summary

Automated testing keeps your usual check in a reusable form. In this example: prepare an enemy, deal 20 damage, and compare HP against 80. Those three are the foundation.

  • Build a Blueprint with Functional Test as the parent and place it in a level.
  • Call the real game logic and compare results with Assert .
  • End with Finish Test and verify both success and failure.

What do you verify with the same steps after every change in your game? Start by keeping just that one check.

Further Reading

For detailed settings and node inputs and outputs, see Epic's Functional Testing, Running automation tests, and Assert Equal (Float).

Unreal Engine Notes in this section98