I'm writing some Minecraft mods and I'd like to write unit tests. Unfortunately, Minecraft forge was not built to be unit tested.
But to that I say: Nay! I will write unit tests!
So Minecraft Forge requires that net.minecraft.launchwrapper.LaunchClassLoader
is used. Virtually any function call in the library will result in an assertion that this loader is being used, throwing an exception in any other case.
To circumvent this, I've been able to use reflection like in the following test class' setup method.
package net.minecraftforge.oredict;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Comparator;
import net.minecraft.block.Block;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.junit.Before;
import org.junit.Test;
import com.google.common.collect.Ordering;
import com.google.common.io.Files;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import cpw.mods.fml.relauncher.Side;
public class RecipeSorterTest {
public static Ordering<IRecipe> ordering = Ordering.from(new Comparator<IRecipe>() {
@Override
public int compare(IRecipe o1, IRecipe o2) {
return RecipeSorter.INSTANCE.compare(o1, o2);
}
});
private LaunchClassLoader lcl;
@Before
public void setUp() throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchFieldException {
Object[] data = new Object[] { "", "", "", "", "1.7.10", "", Files.createTempDir(), Collections.EMPTY_LIST };
URL[] urLs = ((URLClassLoader) Launch.class.getClassLoader()).getURLs();
this.lcl = new LaunchClassLoader(urLs);
Class<?> itemz = lcl.loadClass("cpw.mods.fml.relauncher.FMLRelaunchLog");
Field mz = itemz.getDeclaredField("side");
mz.setAccessible(true);
mz.set(itemz, Enum.valueOf((Class<Enum>) mz.getType(), "CLIENT"));
Class<?> item1 = lcl.loadClass("cpw.mods.fml.common.Loader");
Method m1 = item1.getMethod("injectData", Object[].class);
m1.invoke(null, new Object[] { data });
Class<?> item = lcl.loadClass("net.minecraft.item.Item");
Method m = item.getMethod("registerItems");
m.invoke(null);
// Here's what I'd rather do
// JUnit.setMyClassLoader(new LaunchClassLoader(urLs))
// Loader.injectData(data)
// Item.registerItems();
// Block.registerBlocks();
}
@Test
public void myTest() {
}
}
But this is annoying, hard to maintain, and hard to trust. So I'd like to find a way to simply make my JUnit tests use the LaunchClassLoader
to load classes.
I've seen this answer: Using different classloaders for different JUnit tests?
But it doesn't seem to be solving the problem I have. And even when I tried it, using the LaunchClassLoader
instead of the TestClassLoader
described, I could not run any unit tests because I got a "no runnable methods" error whenever I would try.
Is there an easy way to set the class loader on JUnit tests? (No I cannot change the codebase, this has to be done from the unit tests)