Engine for tests, that requires minecraft to be loaded.
Just add next line after apply plugin: 'forge'
:
apply from: 'https://raw.githubusercontent.com/MJaroslav/MCInGameTester/master/gradle/configurations/v1.gradle'
Note: This build script extension just adds some tasks and last project version to dependencies. If you want to use specific version of engine, add next to your build script:
configurations.all {
resolutionStrategy {
force 'com.github.MJaroslav:MCInGameTester:VERSION:dev'
}
}
Where VERSION
is a selected engine version.
testClient
- runs client with test engine and test source set added in classpath.testServer
- runs server with test engine and test source set added in classpath.testJar
- builds temp jar from test source set. You no needs in this, its utility task.
Note: Any game test task will stop game after all test execution if configuration don't say else.
All that you need for making tests, contains in com.github.mjaroslav.mcingametester.api
(API) package.
If you need to log something, use com.github.mjaroslav.mcingametester.lib.ModInfo#LOG
logger.
Test-like method - it's a non-static void method without parameters.
Test container - it's a simple class with test-like methods.
Note: All annotated things by annotations from API in test containers can be with any access modifier.
For begin, just create test containers in your test source set and then annotate them with @Client
(for only client
side), @Server
(for only server side) or @Common
(for both side).
Note: For these three annotations, you can use when
parameter with LoaderState
type for setting loading state
when tests should be run.
Now, you can write tests in these classes. Just write test-like methods and annotate them with @Test
.
If you needs in executing something before or after each test, you can make test-like method but annotate it
with @BeforeEach
or @AfterEach
instead of @Test
.
If you needs in executing something before or after all tests in class, you can make static test-like method but
annotate it with @BeforeClass
or @AfterClass
instead of @Test
.
In addition, for Server sided tests you can make non-static World-typed field with @WorldShadow
annotation. It's sets
Overworld World object to this field.
Note: Only logger from ModInfo
showed in game test tasks by default.
// Tests from this test container will executed on both sides.
@Common
public class TestCommomSide {
// Be careful, all before/after methods should not throwing any exceptions.
@BeforeClass
static void beforeClass() {
// Static imported LOG from ModInfo.
LOG.info("Will executed on both sides before all tests from this class");
}
@Test
void test$common() {
LOG.info("Will executed on both sides");
}
}
// Tests from this test container will executed only on server side.
@Server(when = LoaderState.INITIALIZATION) // Tests will executed in end (after other mods) of initialization state.
public class TestServerSide {
@WorldShadow
World overworld; // Getter for server Overworld (dim 0).
@AfterEach
void afterEach() {
LOG.info("Will executed after each test of this class on server side");
}
@Test
void test$server() {
LOG.info("Will executed on server side");
}
}
Every test can return one of three results:
SUCCESS
- expected throwable was thrown (if present byexpected
parameter in@Test
) and anything else wasn't thrown.FAILED
- expected throwable wasn't thrown (if present byexpected
parameter in@Test
) orAssertionError
was thrown and anything else wasn't thrown.ERROR
- just error in test engine, for example bad test syntax. It will crash the game.
Note: For not throwing AssertionErrors in tests manually, you can use Assert
class from API. You can also use same
name class from JUnit or any helper for throwing AssertionError
from another libs.
@Test(expected = ClassNotFoundException.class)
void test$clientClassOnServer() throws ClassNotFoundException {
// Test will cause ClassNotFoundException on server and fail test.
// All unexpected exceptions will wrapped in AssertionError and cause test fail.
Class.forName("net.minecraft.client.Minecraft");
// Use deobf names without any problems.
// I hope you don't run this in obfuscated environment, do you?
}
@Test
void test$isServerSide() {
// Fails test if condition return false.
Assert.isTrue(FMLCommonHandler.instance().getSide().isServer(), "Non server side");
}
@WorldShadow // Only for server side test containers.
World overworld;
@Test
void test$shadowWorld() {
// You can combine several assertions.
Assert.isTrue(overworld != null, "Null world");
Assert.isEquals(overworld.provider.dimensionId, 0, "Not Overworld");
}
For run tests manually you should execute testClient
and/or testServer
task.
You can change next options of engine by system environments or JVM system properties (-Dkey=value
):
Parameter | Description | Default value |
---|---|---|
MCIGT_STOP_AFTER_SUCCESS or MCInGameTester.stopAfterSuccess |
Stop game after success tests execution. | true |
MCIGT_FORCED_GAME_STOP_STATE or MCInGameTester.forcedGameStopState |
Forces LoaderState for game stopping. All tests in test containers where when parameter set with LoaderState that should be after forced, will ignored. |
Used max value from annotations when parameter. |
MCIGT_STOP_NO_TESTS or MCInGameTester.stopNoTests |
Stop game if no tests loaded. | true |
MCIGT_STOP_FIRST_FAIL or MCInGameTester.stopFirstFail |
Stop game on first failed test. | false |
MCIGT_HALT_EXIT or MCInGameTester.haltExit |
Use System.halt instead of System.exit for game stopping. | false |
Note: Value from environment variable will force JVM system property value.
You can configure testClient
/testServer
like as runClient
/runServer
tasks (it's JavaExec-typed tasks), but these
tasks contains some additional parameters:
testServer { // or testClient
// Recreate working dir before game launch.
clearWorkingDirBeforeLaunch = true
// Auto accepting eula for server.
eula = true // (false for testClient)
// Use custom Log4J configuration (mcingametester.xml) for showning only ModInfo#LOG logger.
logOnlyTests = true
// Copies mapping to `../conf` of working directory. It's can be useful
// if you use CodeChickenLib in project: version from GregTech repository
// can find mappings automatically, witout showing Java Swing File Chooser.
copyMappingsLocally = true
// In additional, testServer have default "nogui" program argument.
}
Script extension also can set testClient
and testServer
as test
task dependencies if CI
environment variable
is true
, but for client also required environment variable MCIGT_HAS_DISPLAY
with same value. It's required because
OS can be headless and client game will crash. In this case use any display faker for it, for example XVFB.
This action will run all tests (test
with testClient
and testServer
as dependencies) on any push or pull request.
name: Run gradle tests
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Run all tests headless
uses: GabrielBB/xvfb-action@v1
with:
run: ./gradlew test
env:
MCIGT_HAS_DISPLAY: true
Thanks jitpack.io command for adding XVFB to their docker containers by my request.
I use next non-standard settings for this project:
- Environment variable
MCIGT_HAS_DISPLAY
that equalstrue
- Replace test command
by
xvfb-run -e /dev/stdout -s "-screen 0 1280x1024x24 -ac -nolisten tcp -nolisten unix" -a ./gradlew test
Note: Disable dependency cache in init command if you have cache('http')
or something problem.
Note: In jitpack.yml
I use next configuration for building without CI (but you can try use XVFB too):
jdk:
- openjdk8
install:
- ./gradlew build publishToMavenLocal
env:
CI: false
- "Engine".
- Run tasks.
- CI integration.
- Real Gradle Plugin.
- JUtil implementation.
- JUnit report implementation.
- Port to next game versions without alternative frameworks.
- Implement fake display or make mocked client graphics.
Feel free to correct typos and errors in the text or code :)