Skip to content

Commit e08a90a

Browse files
committed
Add assertCommandRunsWithArguments methods to CommandAPITestUtilities
Add CommandAPIHandlerSpy and ExecutionQueue to intercept and track command executions Add AssertArgumentUtilitiesTests and ExecutionQueueTests
1 parent 4fe10d5 commit e08a90a

File tree

9 files changed

+449
-20
lines changed

9 files changed

+449
-20
lines changed

commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public CommandAPIPlatform<Argument, CommandSender, Source> getPlatform() {
187187
* @return a brigadier command which is registered internally
188188
* @throws CommandSyntaxException if an error occurs when the command is ran
189189
*/
190-
Command<Source> generateCommand(Argument[] args, CommandAPIExecutor<CommandSender, AbstractCommandSender<? extends CommandSender>> executor, boolean converted) {
190+
public Command<Source> generateCommand(Argument[] args, CommandAPIExecutor<CommandSender, AbstractCommandSender<? extends CommandSender>> executor, boolean converted) {
191191

192192
// Generate our command from executor
193193
return cmdCtx -> {

commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/pom.xml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@
6262
<scope>provided</scope>
6363
</dependency>
6464

65-
<!-- Compatibility with JUnit API -->
65+
<!-- Use Mockito, pass to dependents if they don't provide it themselves -->
6666
<dependency>
67-
<groupId>org.junit.jupiter</groupId>
68-
<artifactId>junit-jupiter-engine</artifactId>
69-
<version>5.8.2</version>
70-
<scope>provided</scope>
67+
<groupId>org.mockito</groupId>
68+
<artifactId>mockito-core</artifactId>
69+
<version>5.11.0</version>
70+
<scope>compile</scope>
7171
</dependency>
7272

7373
<!-- Pass Brigadier to our dependents -->
@@ -87,6 +87,14 @@
8787
<version>1.8.0-beta4</version>
8888
<scope>compile</scope>
8989
</dependency>
90+
91+
<!-- Run our own tests with JUnit API -->
92+
<dependency>
93+
<groupId>org.junit.jupiter</groupId>
94+
<artifactId>junit-jupiter-engine</artifactId>
95+
<version>5.8.2</version>
96+
<scope>test</scope>
97+
</dependency>
9098
</dependencies>
9199

92100
<build>

commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/CommandAPITestUtilities.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22

33
import com.google.errorprone.annotations.CanIgnoreReturnValue;
44
import com.mojang.brigadier.exceptions.CommandSyntaxException;
5+
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
6+
import dev.jorel.commandapi.executors.ExecutionInfo;
7+
import dev.jorel.commandapi.spying.ExecutionQueue;
58
import org.bukkit.command.CommandSender;
69
import org.opentest4j.AssertionFailedError;
710

11+
import java.util.Map;
812
import java.util.Objects;
913

10-
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
11-
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import static org.junit.jupiter.api.Assertions.*;
1215

1316
public class CommandAPITestUtilities {
1417
public static MockCommandAPIBukkit getCommandAPIPlatform() {
1518
return MockCommandAPIBukkit.getInstance();
1619
}
1720

21+
// Running commands
1822
public static void dispatchCommand(CommandSender sender, String command) throws CommandSyntaxException {
1923
getCommandAPIPlatform().getBrigadierDispatcher().execute(command, new MockCommandSource(sender));
2024
}
@@ -42,4 +46,32 @@ public static CommandSyntaxException assertCommandFails(CommandSender sender, St
4246
}
4347
return exception;
4448
}
49+
50+
// Verifying arguments
51+
public static ExecutionInfo<CommandSender, AbstractCommandSender<? extends CommandSender>> getExecutionInfoOfCommand(
52+
CommandSender sender, String command
53+
) {
54+
ExecutionQueue executions = getCommandAPIPlatform().getCommandAPIHandlerSpy().getExecutionQueue();
55+
executions.clear();
56+
57+
assertCommandSucceeds(sender, command);
58+
59+
ExecutionInfo<CommandSender, AbstractCommandSender<? extends CommandSender>> execution = executions.poll();
60+
assertNotNull(execution);
61+
executions.assertNoMoreCommandsWereRun();
62+
63+
return execution;
64+
}
65+
66+
public static void assertCommandRunsWithArguments(CommandSender sender, String command, Object... argumentsArray) {
67+
assertArrayEquals(argumentsArray, getExecutionInfoOfCommand(sender, command).args().args(),
68+
"Argument arrays are not equal"
69+
);
70+
}
71+
72+
public static void assertCommandRunsWithArguments(CommandSender sender, String command, Map<String, Object> argumentsMap) {
73+
assertEquals(argumentsMap, getExecutionInfoOfCommand(sender, command).args().argsMap(),
74+
"Argument maps are not equal"
75+
);
76+
}
4577
}

commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test-toolkit/src/main/java/dev/jorel/commandapi/MockCommandAPIBukkit.java

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
import dev.jorel.commandapi.arguments.SuggestionProviders;
1212
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
1313
import dev.jorel.commandapi.commandsenders.BukkitCommandSender;
14-
import dev.jorel.commandapi.wrappers.*;
14+
import dev.jorel.commandapi.spying.CommandAPIHandlerSpy;
1515
import dev.jorel.commandapi.wrappers.Rotation;
16+
import dev.jorel.commandapi.wrappers.*;
1617
import net.kyori.adventure.text.Component;
1718
import net.kyori.adventure.text.format.NamedTextColor;
1819
import net.md_5.bungee.api.chat.BaseComponent;
@@ -35,6 +36,7 @@
3536

3637
import java.io.File;
3738
import java.io.IOException;
39+
import java.lang.reflect.Field;
3840
import java.util.*;
3941
import java.util.function.Function;
4042
import java.util.function.Predicate;
@@ -51,27 +53,49 @@ public MockCommandAPIBukkit() {
5153
MockCommandAPIBukkit.instance = this;
5254
}
5355

56+
// Reflection helpers
57+
public static <Target> void setField(Class<? super Target> targetClass, String fieldName, Target target, Object value) {
58+
try {
59+
Field field = targetClass.getDeclaredField(fieldName);
60+
field.setAccessible(true);
61+
field.set(target, value);
62+
} catch (ReflectiveOperationException e) {
63+
throw new IllegalArgumentException("Reflection failed", e);
64+
}
65+
}
66+
5467
// References to utility classes
68+
private CommandAPIHandlerSpy commandAPIHandlerSpy;
5569
private CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource> commandAPIHandler;
5670
private MockCommandRegistrationStrategy commandRegistrationStrategy;
5771

5872
@Override
5973
public void onLoad(CommandAPIConfig<?> config) {
60-
this.commandAPIHandler = (CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource>) CommandAPIHandler.getInstance();
61-
this.commandRegistrationStrategy = new MockCommandRegistrationStrategy(commandAPIHandler);
74+
// Intercept calls to CommandAPIHandler
75+
commandAPIHandlerSpy = new CommandAPIHandlerSpy(CommandAPIHandler.getInstance());
76+
commandAPIHandler = commandAPIHandlerSpy.spyHandler();
77+
setField(CommandAPIHandler.class, "instance", null, commandAPIHandler);
78+
79+
// Setup objects
80+
commandRegistrationStrategy = new MockCommandRegistrationStrategy(commandAPIHandler);
6281

82+
// Continue load
6383
super.onLoad(config);
6484
}
6585

86+
public CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource> getCommandAPIHandler() {
87+
return commandAPIHandler;
88+
}
89+
90+
public CommandAPIHandlerSpy getCommandAPIHandlerSpy() {
91+
return commandAPIHandlerSpy;
92+
}
93+
6694
@Override
6795
public CommandRegistrationStrategy<MockCommandSource> createCommandRegistrationStrategy() {
6896
return commandRegistrationStrategy;
6997
}
7098

71-
public CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource> getCommandAPIHandler() {
72-
return commandAPIHandler;
73-
}
74-
7599
// CommandSender/MockCommandSource methods
76100
@Override
77101
public BukkitCommandSender<? extends CommandSender> getSenderForCommand(CommandContext<MockCommandSource> cmdCtx, boolean forceNative) {
@@ -84,6 +108,11 @@ public BukkitCommandSender<? extends CommandSender> getCommandSenderFromCommandS
84108
return super.wrapCommandSender(cs.bukkitSender());
85109
}
86110

111+
@Override
112+
public MockCommandSource getBrigadierSourceFromCommandSender(AbstractCommandSender<? extends CommandSender> sender) {
113+
return new MockCommandSource(sender.getSource());
114+
}
115+
87116
// Logging
88117
public boolean ENABLE_LOGGING = false;
89118

@@ -98,11 +127,6 @@ public CommandAPILogger getLogger() {
98127
// UNIMPLEMENTED METHODS //
99128
///////////////////////////
100129

101-
@Override
102-
public MockCommandSource getBrigadierSourceFromCommandSender(AbstractCommandSender<? extends CommandSender> sender) {
103-
throw new UnimplementedMethodException();
104-
}
105-
106130
@Override
107131
public ArgumentType<?> _ArgumentAngle() {
108132
throw new UnimplementedMethodException();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package dev.jorel.commandapi.spying;
2+
3+
import com.mojang.brigadier.Command;
4+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
5+
import dev.jorel.commandapi.CommandAPIExecutor;
6+
import dev.jorel.commandapi.CommandAPIHandler;
7+
import dev.jorel.commandapi.MockCommandSource;
8+
import dev.jorel.commandapi.arguments.AbstractArgument;
9+
import dev.jorel.commandapi.arguments.Argument;
10+
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
11+
import org.bukkit.command.CommandSender;
12+
import org.mockito.Mockito;
13+
14+
import static org.mockito.ArgumentMatchers.any;
15+
16+
public class CommandAPIHandlerSpy {
17+
// Handler instances
18+
private final CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource> handler;
19+
private final CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource> spyHandler;
20+
21+
public CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource> spyHandler() {
22+
return spyHandler;
23+
}
24+
25+
// Methods for handling intercepts
26+
ExecutionQueue executionQueue = new ExecutionQueue();
27+
28+
public ExecutionQueue getExecutionQueue() {
29+
return executionQueue;
30+
}
31+
32+
// Setup
33+
public CommandAPIHandlerSpy(CommandAPIHandler<?, ?, ?> commandAPIHandler) {
34+
handler = (CommandAPIHandler<Argument<?>, CommandSender, MockCommandSource>) commandAPIHandler;
35+
spyHandler = Mockito.spy(handler);
36+
37+
Mockito.when(spyHandler.generateCommand(any(), any(), any(Boolean.class) /* Class gives non-null un-boxable default */))
38+
.thenAnswer(i -> generateCommand(i.getArgument(0), i.getArgument(1), i.getArgument(2)));
39+
}
40+
41+
// Intercepted methods
42+
private Command<MockCommandSource> generateCommand(
43+
AbstractArgument<?, ?, ?, ?>[] args, // Using AbstractArgument[] because that's the actual runtime type of the array
44+
CommandAPIExecutor<CommandSender, AbstractCommandSender<? extends CommandSender>> executor,
45+
boolean converted
46+
) {
47+
CommandAPIExecutor<CommandSender, AbstractCommandSender<? extends CommandSender>> spyExecutor = Mockito.spy(executor);
48+
49+
try {
50+
// Not using Mockito.when to avoid calling real executes method
51+
Mockito.doAnswer(i -> {
52+
executionQueue.add(i.getArgument(0));
53+
return i.callRealMethod();
54+
}).when(spyExecutor).execute(any());
55+
} catch (CommandSyntaxException ignored) {
56+
// `spyExecutor#execute` will never actually throw an exception
57+
}
58+
59+
// Convert array to Argument<?>[], which is what we actually want
60+
Argument<?>[] arguments = new Argument[args.length];
61+
System.arraycopy(args, 0, arguments, 0, args.length);
62+
return handler.generateCommand(arguments, spyExecutor, converted);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dev.jorel.commandapi.spying;
2+
3+
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
4+
import dev.jorel.commandapi.executors.ExecutionInfo;
5+
import org.bukkit.command.CommandSender;
6+
import org.opentest4j.AssertionFailedError;
7+
8+
import java.util.LinkedList;
9+
import java.util.Queue;
10+
11+
public class ExecutionQueue {
12+
Queue<ExecutionInfo<CommandSender, AbstractCommandSender<? extends CommandSender>>> queue = new LinkedList<>();
13+
14+
public void clear() {
15+
queue.clear();
16+
}
17+
18+
public void add(ExecutionInfo<CommandSender, AbstractCommandSender<? extends CommandSender>> info) {
19+
queue.add(info);
20+
}
21+
22+
public ExecutionInfo<CommandSender, AbstractCommandSender<? extends CommandSender>> poll() {
23+
return queue.poll();
24+
}
25+
26+
public void assertNoMoreCommandsWereRun() {
27+
if (!queue.isEmpty()) {
28+
throw new AssertionFailedError("Expected no more commands to be run, but found " + queue.size() + " command(s) left");
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)