🇬🇧 English
🇬🇧 English
Appearance
🇬🇧 English
🇬🇧 English
Appearance
This page is written for version:
1.21
This page is written for version:
1.21
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.
WARNING
Currently, this guide only covers unit testing.
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/