Writing JUnit Tests
Here you can find a summary on how to write JUnit tests for BetonQuest. In order to understand this, you need to have basic knowledge of JUnit tests and mocking of objects and classes.
It is a major goal to write JUnit tests for most parts of BetonQuest.
There are things, where you definitely want as many tests as possible:
- Internal application logic that is used by a bunch of other code
- Critical parts that can cause a lot of harm when bugged
But there are also some parts, where we do not want tests at all:
- If a core API concept has many implementations, the implementations itself should not be tested
- Some parts of the code require a lot of Bukkit API mocking. If this takes too much time no tests are necessary
By default, all classes and methods are executed
CONCURRENT. This means that its tests are run in parallel which saves
time. Some tests cannot be executed in parallel, in such cases the following annotation needs to be
added to the related methods or the entire class.
You may need to read Logging to understand this paragraph.
This error can occur everytime the
@CustomLog annotation is used in a class that is called by a JUnit test:
Cannot invoke "org.bukkit.Server.getPluginManager()" because "org.bukkit.Bukkit.server" is null
If this is the case, you need the
BetonQuestLoggerService. Simply add the following annotation to the
The test should now work as intended because a new anonymous logger is created everytime the
is used. All these loggers have a silent parent logger - so there are no visible log messages in the command line. The
BetonQuestLoggerService also enables a few new features:
You can now add this optional argument to any test's method signature:
1 2 3 4 5
LogValidator is created and passed to your method by the
It makes it possible to assert that a log message has been logged in the silent parent logger.
The simplest method is
assertLogEntry(Level level, String message), that you can use to check
that the given message with the given level has been logged. You can also check that there are no additional log
messages in the
LogValidator by calling
When using the
LogValidator, you validate that log messages are logged in the correct order. This means that
if you leave the
ExecutionMode on its default (
CONCURRENT) value, the test will fail. This happens because the
log messages don't have a predictable order as your tests would be executed in parallel.
Obtaining the parent
Logger and a
You can also use these two additional arguments:
1 2 3 4 5
logger is the silent parent
log is a new instance of the
BetonQuestLogger that you can use to log things during the test.
This logger has a topic that can be accessed via
If you want to test code that only works with the
BukkitScheduler, we even have a ready to use solution for this.
To use the
BukkitSchedulerMock you need to create the following setup:
1 2 3 4 5 6
Now you can use the scheduler object for several things. First if you want to perform a single or multiple ticks,
you can call the methods
We also have a method that allows to get the number of ticks since the
BukkitSchedulerMock was created.
There are some additional features of this scheduler:
1 2 3
- Shuts down the scheduler. Already called thanks to with "try with resources".
- Wait for all async tasks to finish.
- One second timeout.
Expanded visibility for testing🔗
Sometimes you need a method, class or field to be accessible for your JUnit tests but not for external code.
Generally a good way to achieve this is using the default (package-local) access modifier instead of
Of course the unit tests must be located in the same package for this to work.
To clearly mark such elements, that are more widely visible than necessary only for use in test code,
@VisibleForTesting annotation can be added.
Make sure you import it from
org.betonquest.betonquest.api.annotation, not from Google Commons or Apache.
This will also suppress the PMD rule
which requires you to add a
/* default */ or
/* package */ comment when using default access modifier.