1.16 Unit Test: Weather 2 - Part 1

7 min read

Introduction

The 1.16 unit test: weather 2 – part 1 explores how to verify Minecraft’s weather‑related mechanics using the built‑in JUnit framework introduced in the 1.16 update. In real terms, by the end of this guide you will understand why weather testing matters, how to set up a reliable test environment, and which core APIs you need to manipulate and assert the state of rain, thunder, and clear skies. This article is written for developers who are already familiar with basic Java unit testing and want to deepen their knowledge of Minecraft’s server‑side weather system.


Why Unit Test Weather in Minecraft?

  • Predictable gameplay – Weather influences mob spawning, crop growth, and player visibility. A regression in the weather cycle can break farms or PvP arenas.
  • Performance safety – The weather scheduler runs every tick. Faulty logic may cause unnecessary server load or even crashes.
  • Mod compatibility – Many mods hook into ServerWorld#setWeather or WeatherManager. Automated tests guarantee that your changes do not break existing contracts.

By writing focused unit tests, you catch these issues early, keep the codebase stable, and gain confidence when releasing new features or bug‑fixes.


Setting Up the Test Environment

1. Add the Testing Dependency

In your build.gradle (or pom.xml) add the Minecraft test harness:

dependencies {
    testImplementation "net.minecraft:server:1.16.5"
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2"
    testRuntimeOnly    "org.junit.jupiter:junit-jupiter-engine:5.8.2"
}

2. Enable the JUnit Platform

test {
    useJUnitPlatform()
}

3. Create the Base Test Class

package com.example.weather;

import net.junit.Think about it: world. minecraft.math.On top of that, minecraft. BlockPos;
import net.MinecraftServer;
import net.ServerWorld;
import net.world.util.minecraft.server.Here's the thing — server. minecraft.jupiter.World;
import org.api.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class WeatherTestBase {

    protected MinecraftServer server;
    protected ServerWorld world;

    @BeforeAll
    void startServer() throws Exception {
        // Initialise a headless server instance
        server = TestServerFactory.createServer(); // provided by the test harness
        world = server.getWorld(World.

    @AfterAll
    void stopServer() {
        server.shutdown();
    }

    /** Helper to advance ticks */
    protected void tick(int amount) {
        for (int i = 0; i < amount; i++) {
            server.tick();
        }
    }
}

The TestServerFactory is part of the official test harness and creates a lightweight, in‑memory server that runs without a GUI. This keeps the test fast and deterministic But it adds up..


Core Weather API Overview

Method Description Typical Use
ServerWorld#setWeather(int clearTicks, int rainTicks, int thunderTicks) Directly sets the remaining duration for each weather state. Even so, Force a specific weather condition for a test. That's why
ServerWorld#getRainLevel(float delta) Returns the current rain intensity (0‑1). Consider this: Verify smooth transitions.
ServerWorld#getThunderLevel(float delta) Returns the current thunder intensity (0‑1). Check lightning‑related logic.
ServerWorld#isRaining() Boolean shortcut for getRainLevel() > 0. But Simple assertions about rain presence. But
ServerWorld#isThundering() Boolean shortcut for getThunderLevel() > 0. Simple assertions about thunder.

Counterintuitive, but true.

All these methods are tick‑based, meaning they change gradually over several server ticks. A strong unit test must therefore simulate tick progression Easy to understand, harder to ignore..


Writing the First Weather Test

Test Goal

Validate that invoking setWeather(0, 6000, 0) correctly starts a rain period that lasts exactly 6000 ticks (5 minutes in‑game) and that the rain level ramps up to 1.0 within the first 1200 ticks (the default transition time) No workaround needed..

Implementation

package com.example.weather;

import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class WeatherTransitionTest extends WeatherTestBase {

    @Test
    @DisplayName("Rain starts and reaches full intensity after the transition period")
    void rainStartsAndReachesFullIntensity() {
        // Arrange – clear sky, ensure no rain or thunder
        world.setWeather(20000, 0, 0);
        tick(5); // let the server settle

        assertFalse(world.isRaining(), "World should start clear");
        assertEquals(0f, world.getRainLevel(0f), "Initial rain level must be 0");

        // Act – trigger rain for 6000 ticks
        world.setWeather(0, 6000, 0);

        // Simulate the transition period (1200 ticks)
        tick(1200);

        // Assert – rain should now be at full intensity
        assertTrue(world.isRaining(), "World should be raining after transition");
        assertEquals(1f, world.Practically speaking, getRainLevel(0f), 0. 01f, "Rain level must be ~1.

        // Verify remaining rain duration
        int remainingRainTicks = world.getRainTime(); // accessor from mixin or reflection
        assertEquals(4800, remainingRainTicks, "6000 - 1200 transition = 4800 ticks left");
    }
}

Key points in the test

  • Arrange: Reset the world to a known clear state.
  • Act: Call setWeather with the desired parameters.
  • Assert: Use tick to advance the server and then check both boolean shortcuts and the floating‑point intensity values.

The small tolerance (0.01f) accounts for floating‑point rounding that occurs during the transition.


Testing Thunderstorm Behavior

Thunderstorms combine rain with a separate thunder counter. Also, the official game uses a 0‑1 thunder intensity that lags behind rain by a configurable offset (default 0. 5 seconds). To verify this relationship, we write a second test.

@Test
@DisplayName("Thunderstorm initiates with lagged thunder intensity")
void thunderstormLag() {
    // Clear any previous weather
    world.setWeather(20000, 0, 0);
    tick(5);

    // Start thunderstorm: 6000 ticks rain + 6000 ticks thunder
    world.setWeather(0, 6000, 6000);

    // After 1200 ticks, rain should be full but thunder only halfway
    tick(1200);
    assertTrue(world.That's why isRaining(), "Rain should be active");
    assertEquals(1f, world. getRainLevel(0f), 0.

    // Thunder intensity after the same period (should be ≈0.5)
    assertEquals(0.Which means 5f, world. getThunderLevel(0f), 0.

The test demonstrates that **thunder intensity lags** behind rain, matching the in‑game visual cue where lightning appears after the clouds have thickened.

---

## Simulating Random Weather Changes  

Minecraft’s weather scheduler uses a pseudo‑random generator to decide when to start a new weather event. To test this logic without relying on true randomness, we can inject a deterministic `Random` instance via reflection (the server stores it in `World#random`).  

```java
private void injectRandom(Random deterministic) throws Exception {
    Field randomField = ServerWorld.class.getDeclaredField("random");
    randomField.setAccessible(true);
    randomField.set(world, deterministic);
}

Test example – forcing a clear‑to‑rain transition

@Test
@DisplayName("Deterministic random generator triggers rain after expected ticks")
void deterministicRainTrigger() throws Exception {
    // Force the random to always return 0 (minimum value)
    injectRandom(new Random(0L) {
        @Override public int nextInt(int bound) { return 0; }
    });

    // Reset weather and run the scheduler for 24000 ticks (20 minutes)
    world.setWeather(24000, 0, 0);
    tick(24000);

    // The scheduler should have started rain because the random check succeeded
    assertTrue(world.isRaining(), "Rain must have started due to deterministic random");
}

By controlling the randomness, the test becomes repeatable, a cornerstone of reliable unit testing.


Common Pitfalls and How to Avoid Them

  1. Skipping Tick Advancement – Many developers call setWeather and immediately assert the state. Because weather transitions are gradual, the test will fail. Always use tick(int) to simulate the required number of server ticks.
  2. Using the Client World – The client (ClientWorld) mirrors server weather but does not expose the same setters. Unit tests must run on the server side (ServerWorld).
  3. Neglecting the delta ParametergetRainLevel(float delta) interpolates between the previous and current tick. Passing 0f gives the exact tick value; using a non‑zero delta without understanding its effect can lead to flaky assertions.
  4. Hard‑coding Tick Values – The transition period may change in future Minecraft releases. Prefer using the constants defined in WeatherManager (RAIN_TRANSITION_TICKS, THUNDER_TRANSITION_TICKS) via reflection or a helper method, so the test adapts automatically.

FAQ

Q1: Do I need a full server instance for weather tests?
A: Yes. Weather logic lives in ServerWorld and depends on the tick loop. The lightweight headless server provided by the test harness is sufficient and runs in a few hundred milliseconds.

Q2: Can I test client‑side visual effects (e.g., particle rain)?
A: Not with pure unit tests. Those require integration or UI tests using the Minecraft client and a rendering framework. The scope of unit test: weather 2 is limited to server‑side state.

Q3: How do I assert that lightning strikes actually occur?
A: Lightning spawning is handled by WeatherManager#tickThunder. You can spy on the ServerWorld#spawnEntity method using a mocking library (e.g., Mockito with a mixin) and verify that a LightningEntity is created after the thunder counter reaches the threshold.

Q4: Is it safe to modify the vanilla Random field?
A: Modifying the field via reflection is safe within the isolated test environment. Ensure you restore the original instance after the test if you share the world across multiple test classes.

Q5: What version of JUnit should I use?
A: JUnit 5 (Jupiter) is recommended because it integrates cleanly with Gradle’s useJUnitPlatform() and provides better lifecycle control for server setup/teardown And that's really what it comes down to..


Conclusion

The 1.16 unit test: weather 2 – part 1 tutorial has walked you through the essential steps to create deterministic, fast, and maintainable tests for Minecraft’s weather system. By:

  1. Setting up a headless server environment,
  2. Understanding the key weather APIs,
  3. Simulating tick progression,
  4. Controlling randomness, and
  5. Avoiding common pitfalls,

you now possess a solid foundation for expanding your test suite to cover more complex scenarios such as biome‑specific precipitation, custom weather mods, and performance regressions. Consistently applying these practices will keep your Minecraft projects stable, future‑proof, and ready for the next update.

Happy testing, and may your skies be clear—or perfectly rainy—exactly when you need them to be.

Just Came Out

Latest and Greatest

People Also Read

One More Before You Go

Thank you for reading about 1.16 Unit Test: Weather 2 - Part 1. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home