🇬🇧 English
🇬🇧 English
Appearance
🇬🇧 English
🇬🇧 English
Appearance
This page is written for version:
1.21.4
This page explains how to write code to automatically test parts of your mod. There are two ways to automatically test your mod: unit tests with Fabric Loader JUnit or game tests with the Gametest framework from Minecraft.
Unit tests should be used to test components of your code, such as methods and helper classes, while game tests spin up an actual Minecraft client and server to run your tests, which makes it suitable for testing features and gameplay.
Since Minecraft modding relies on runtime byte-code modification tools such as Mixin, simply adding and using JUnit normally would not work. That's why Fabric provides Fabric Loader JUnit, a JUnit plugin that enables unit testing in Minecraft.
First, we need to add Fabric Loader JUnit to the development environment. Add the following to your dependencies block in your build.gradle
:
testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
Then, we need to tell Gradle to use Fabric Loader JUnit for testing. You can do so by adding the following code to your build.gradle
:
test {
useJUnitPlatform()
}
Once you reload Gradle, you're now ready to write tests.
These tests are written just like regular JUnit tests, with a bit of additional setup if you want to access any registry-dependent class, such as ItemStack
. If you're comfortable with JUnit, you can skip to Setting Up Registries.
Tests are written in the src/test/java
directory.
One naming convention is to mirror the package structure of the class you are testing. For example, to test src/main/java/com/example/docs/codec/BeanType.java
, you'd create a class at src/test/java/com/example/docs/codec/BeanTypeTest.java
. Notice how we added Test
to the end of the class name. This also allows you to easily access package-private methods and fields.
Another naming convention is to have a test
package, such as src/test/java/com/example/docs/test/codec/BeanTypeTest.java
. This prevents some problems that may arise with using the same package if you use Java modules.
After creating the test class, use ⌘/CTRLN to bring up the Generate menu. Select Test and start typing your method name, usually starting with test
. Press ENTER when you're done. For more tips and tricks on using the IDE, see IDE Tips and Tricks.
You can, of course, write the method signature by hand, and any instance method with no parameters and a void return type will be identified as a test method. You should end up with the following:
Notice the green arrow indicators in the gutter: you can easily run a test by clicking them. Alternately, your tests will run automatically on every build, including CI builds such as GitHub Actions. If you're using GitHub Actions, don't forget to read Setting Up GitHub Actions.
Now, it's time to write your actual test code. You can assert conditions using org.junit.jupiter.api.Assertions
. Check out the following test:
public class BeanTypeTest {
private static final Gson GSON = new GsonBuilder().create();
@BeforeAll
static void beforeAll() {
BeanTypes.register();
}
@Test
void testBeanCodec() {
StringyBean expectedBean = new StringyBean("This bean is stringy!");
Bean actualBean = Bean.BEAN_CODEC.parse(JsonOps.INSTANCE, GSON.fromJson("{\"type\":\"example:stringy_bean\",\"stringy_string\":\"This bean is stringy!\"}", JsonObject.class)).getOrThrow();
Assertions.assertInstanceOf(StringyBean.class, actualBean);
Assertions.assertEquals(expectedBean.getType(), actualBean.getType());
Assertions.assertEquals(expectedBean.getStringyString(), ((StringyBean) actualBean).getStringyString());
}
@Test
void testDiamondItemStack() {
// I know this isn't related to beans, but I need an example :)
ItemStack diamondStack = new ItemStack(Items.DIAMOND, 65);
Assertions.assertTrue(diamondStack.isOf(Items.DIAMOND));
Assertions.assertEquals(65, diamondStack.getCount());
}
}
For an explanation of what this code actually does, see Codecs.
Great, the first test worked! But wait, the second test failed? In the logs, we get one of the following errors.
java.lang.ExceptionInInitializerError
at net.minecraft.item.ItemStack.<clinit>(ItemStack.java:94)
at com.example.docs.codec.BeanTypeTest.testBeanCodec(BeanTypeTest.java:20)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.IllegalArgumentException: Not bootstrapped (called from registry ResourceKey[minecraft:root / minecraft:game_event])
at net.minecraft.Bootstrap.createNotBootstrappedException(Bootstrap.java:118)
at net.minecraft.Bootstrap.ensureBootstrapped(Bootstrap.java:111)
at net.minecraft.registry.Registries.create(Registries.java:238)
at net.minecraft.registry.Registries.create(Registries.java:229)
at net.minecraft.registry.Registries.<clinit>(Registries.java:139)
... 5 more
Not bootstrapped (called from registry ResourceKey[minecraft:root / minecraft:game_event])
java.lang.IllegalArgumentException: Not bootstrapped (called from registry ResourceKey[minecraft:root / minecraft:game_event])
at net.minecraft.Bootstrap.createNotBootstrappedException(Bootstrap.java:118)
at net.minecraft.Bootstrap.ensureBootstrapped(Bootstrap.java:111)
at net.minecraft.registry.Registries.create(Registries.java:238)
at net.minecraft.registry.Registries.create(Registries.java:229)
at net.minecraft.registry.Registries.<clinit>(Registries.java:139)
at net.minecraft.item.ItemStack.<clinit>(ItemStack.java:94)
at com.example.docs.codec.BeanTypeTest.testBeanCodec(BeanTypeTest.java:20)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
This is because we're trying to access the registry or a class that depends on the registry (or, in rare cases, depends on other Minecraft classes such as SharedConstants
), but Minecraft has not been initialized. We just need to initialize it a little bit to have registries working. Simply add the following code to the beginning of your beforeAll
method.
SharedConstants.createGameVersion();
Bootstrap.initialize();
INFO
This section assumes that you are using the standard GitHub Action workflow included with the example mod and with the mod template.
Your tests will now run on every build, including those by CI providers such as GitHub Actions. But what if a build fails? We need to upload the logs as an artifact so we can view the test reports.
Add this to your .github/workflows/build.yml
file, below the ./gradlew build
step.
- name: Store reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: reports
path: |
**/build/reports/
**/build/test-results/
Minecraft provides the game test framework for testing server-side features. Fabric additionally provides client game tests for testing client-side features, similar to an end-to-end test.
Both server and client game tests can be set up manually or with Fabric Loom. This guide will use Loom.
To add game tests to your mod, add the following to your build.gradle
:
fabricApi {
configureTests {
createSourceSet = true
modId = "fabric-docs-reference-test-${project.name}"
eula = true
}
}
To see all available options, see the Loom documentation on tests.
INFO
You only need this section if you enabled createSourceSet
, which is recommended. You can, of course, do your own gradle magic, but you'll be on your own.
If you enabled createSourceSet
like the example above, your gametest will be in a separate source set with a separate fabric.mod.json
. The module name defaults to gametest
. Create a new fabric.mod.json
in src/gametest/resources/
as shown:
{
"schemaVersion": 1,
"id": "fabric-docs-reference-test",
"version": "1.0.0",
"name": "Fabric docs reference",
"icon": "assets/fabric-docs-reference/icon.png",
"environment": "*",
"entrypoints": {
"fabric-gametest": [
"com.example.docs.FabricDocsGameTest"
],
"fabric-client-gametest": [
"com.example.docs.FabricDocsClientGameTest"
]
}
}
Note that this fabric.mod.json
expects a server game test at src/gametest/java/com/example/docs/FabricDocsGameTest
, and a client game test at src/gametest/java/com/example/docs/FabricDocsClientGameTest
.
You can now create server and client game tests in the src/gametest/java
directory. Here is a basic example for each:
package com.example.docs;
import net.minecraft.block.Blocks;
import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
public class FabricDocsGameTest implements FabricGameTest {
@GameTest(templateName = EMPTY_STRUCTURE)
public void test(TestContext context) {
context.expectBlock(Blocks.AIR, 0, 0, 0);
context.complete();
}
}
package com.example.docs;
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
import net.fabricmc.fabric.api.client.gametest.v1.context.TestSingleplayerContext;
@SuppressWarnings("UnstableApiUsage")
public class FabricDocsClientGameTest implements FabricClientGameTest {
@Override
public void runTest(ClientGameTestContext context) {
try (TestSingleplayerContext singleplayer = context.worldBuilder().create()) {
singleplayer.getClientWorld().waitForChunksRender();
context.takeScreenshot("fabric-docs-reference-singleplayer-test");
}
}
}
See the respective Javadocs in Fabric API for more info.