Skip to main content
Flow tests are multi-step integration tests that exercise a complete gameplay sequence across multiple server ticks. They are the closest thing to “playing through a scenario” that you can automate in HRTK.

What Is a Flow Test?

A flow test simulates a sequence of game events and verifies the outcome at each stage. For example:
  1. Spawn an entity in the world
  2. Wait for ECS processors to initialize it
  3. Apply damage to trigger combat logic
  4. Wait for the entity to die
  5. Verify loot drops
Each step may require one or more server ticks to take effect. Flow tests use waitTicks() and awaitCondition() to synchronize with the server’s tick loop.

@FlowTest

The @FlowTest annotation is a shorthand for @HytaleTest + @Tag("flow") with built-in world context and tick timeout:
import com.frotty27.hrtk.api.annotation.FlowTest;

@FlowTest(timeoutTicks = 200)
void spawnAndKill(WorldTestContext ctx) {
    Object entity = ctx.spawnEntity("hytale:kweebec", 0, 64, 0);
    ctx.waitTicks(1);

    killEntity(ctx, entity);
    ctx.waitTicks(5);

    StatsAssert.assertDead(ctx.getStore(), entity);
}
ParameterDefaultDescription
world""World name (empty = test world)
timeoutTicks200Max ticks before timeout (10 seconds at 20 TPS)

Why Tick Waiting Matters

Hytale’s game loop processes everything in discrete ticks. When you call ctx.spawnEntity(), the entity is created in the command buffer but ECS processors have not run yet. Components like health, stats, transform, and effects are initialized by processors that run on subsequent ticks. Without tick waiting:
Object entity = ctx.spawnEntity("hytale:kweebec", 0, 64, 0);
StatsAssert.assertAlive(ctx.getStore(), entity); // FAILS -- no stats yet
With tick waiting:
Object entity = ctx.spawnEntity("hytale:kweebec", 0, 64, 0);
ctx.waitTicks(1);
StatsAssert.assertAlive(ctx.getStore(), entity); // PASSES

Flow Test Structure

A well-structured flow test follows this pattern:
@FlowTest(timeoutTicks = 200)
void myFlowTest(WorldTestContext ctx) {
    // 1. SETUP -- spawn entities, place blocks
    Object entity = ctx.spawnEntity("hytale:kweebec", 0, 64, 0);
    ctx.waitTicks(1);

    // 2. ACTION -- trigger the behavior you're testing
    triggerAction(ctx, entity);

    // 3. WAIT -- let the server process the action
    ctx.waitTicks(5);

    // 4. ASSERT -- verify the expected outcome
    HytaleAssert.assertTrue(expectedResult(ctx, entity));

    // 5. (Optional) SECONDARY ACTION + ASSERT
    triggerSecondAction(ctx, entity);
    ctx.waitTicks(3);
    HytaleAssert.assertTrue(secondExpectedResult(ctx, entity));
}
Use awaitCondition() instead of fixed waitTicks() when you do not know exactly how many ticks an action will take. It polls every tick and returns as soon as the condition is met, making your tests both faster and more reliable.

Isolation for Flow Tests

Flow tests almost always mutate world state. Use IsolationStrategy.DEDICATED_WORLD:
@HytaleSuite(value = "Combat Flows", isolation = IsolationStrategy.DEDICATED_WORLD)
@Tag("flow")
public class CombatFlows {
    // All flow tests run in a temporary void world
}

Next Steps

Spawn-Kill-Loot Flow

A complete flow example: spawn, tier, kill, and verify loot drops.

Writing Custom Flows

How to structure your own multi-step flow tests.