Skip to content

Commit b98f37f

Browse files
committed
Enhanced JDA mocking test suite
* support for `OptionType.INTEGER` in slash command event builder * `getMemberSpy()` * `getJdaMock()` * `createTextChannelSpy` * `getPrivateChannelSpy()` and mocking of private channel in general * `map` and `flatMap` support for `succeeded` and `failed` actions
1 parent b8a270d commit b98f37f

File tree

2 files changed

+129
-4
lines changed

2 files changed

+129
-4
lines changed

application/src/test/java/org/togetherjava/tjbot/jda/JdaTester.java

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.togetherjava.tjbot.jda;
22

33
import net.dv8tion.jda.api.AccountType;
4+
import net.dv8tion.jda.api.JDA;
45
import net.dv8tion.jda.api.Permission;
56
import net.dv8tion.jda.api.entities.*;
67
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -38,6 +39,7 @@
3839
import java.util.concurrent.ScheduledExecutorService;
3940
import java.util.concurrent.ScheduledThreadPoolExecutor;
4041
import java.util.function.Consumer;
42+
import java.util.function.Function;
4143
import java.util.function.Supplier;
4244
import java.util.function.UnaryOperator;
4345

@@ -130,6 +132,7 @@ public JdaTester() {
130132
doReturn(APPLICATION_ID).when(selfUser).getApplicationIdLong();
131133
doReturn(selfUser).when(jda).getSelfUser();
132134
when(jda.getGuildChannelById(anyLong())).thenReturn(textChannel);
135+
when(jda.getTextChannelById(anyLong())).thenReturn(textChannel);
133136
when(jda.getPrivateChannelById(anyLong())).thenReturn(privateChannel);
134137
when(jda.getGuildById(anyLong())).thenReturn(guild);
135138
when(jda.getEntityBuilder()).thenReturn(entityBuilder);
@@ -140,8 +143,6 @@ public JdaTester() {
140143
doReturn(new Requester(jda, new AuthorizationConfig(TEST_TOKEN))).when(jda).getRequester();
141144
when(jda.getAccountType()).thenReturn(AccountType.BOT);
142145

143-
doReturn(messageAction).when(privateChannel).sendMessage(anyString());
144-
145146
replyAction = mock(ReplyCallbackActionImpl.class);
146147
when(replyAction.setEphemeral(anyBoolean())).thenReturn(replyAction);
147148
when(replyAction.addActionRow(anyCollection())).thenReturn(replyAction);
@@ -155,7 +156,6 @@ public JdaTester() {
155156
auditableRestAction = (AuditableRestActionImpl<Void>) mock(AuditableRestActionImpl.class);
156157
doNothing().when(auditableRestAction).queue();
157158

158-
doNothing().when(messageAction).queue();
159159
doNothing().when(webhookMessageUpdateAction).queue();
160160
doReturn(webhookMessageUpdateAction).when(webhookMessageUpdateAction)
161161
.setActionRow(any(ItemComponent.class));
@@ -164,6 +164,9 @@ public JdaTester() {
164164
doReturn(selfMember).when(guild).getMember(selfUser);
165165
doReturn(member).when(guild).getMember(not(eq(selfUser)));
166166

167+
RestAction<User> userAction = createSucceededActionMock(member.getUser());
168+
when(jda.retrieveUserById(anyLong())).thenReturn(userAction);
169+
167170
doReturn(null).when(textChannel).retrieveMessageById(any());
168171

169172
interactionHook = mock(InteractionHook.class);
@@ -172,6 +175,20 @@ public JdaTester() {
172175
.thenReturn(webhookMessageUpdateAction);
173176
when(interactionHook.editOriginal(any(byte[].class), any(), any()))
174177
.thenReturn(webhookMessageUpdateAction);
178+
179+
doReturn(messageAction).when(textChannel).sendMessageEmbeds(any(), any());
180+
doReturn(messageAction).when(textChannel).sendMessageEmbeds(any());
181+
182+
doNothing().when(messageAction).queue();
183+
when(messageAction.content(any())).thenReturn(messageAction);
184+
185+
RestAction<PrivateChannel> privateChannelAction = createSucceededActionMock(privateChannel);
186+
when(jda.openPrivateChannelById(anyLong())).thenReturn(privateChannelAction);
187+
when(jda.openPrivateChannelById(anyString())).thenReturn(privateChannelAction);
188+
doReturn(null).when(privateChannel).retrieveMessageById(any());
189+
doReturn(messageAction).when(privateChannel).sendMessage(anyString());
190+
doReturn(messageAction).when(privateChannel).sendMessageEmbeds(any(), any());
191+
doReturn(messageAction).when(privateChannel).sendMessageEmbeds(any());
175192
}
176193

177194
/**
@@ -246,6 +263,8 @@ public JdaTester() {
246263

247264
/**
248265
* Creates a Mockito spy for a member with the given user id.
266+
* <p>
267+
* See {@link #getMemberSpy()} to get the default member used by this tester.
249268
*
250269
* @param userId the id of the member to create
251270
* @return the created spy
@@ -255,6 +274,18 @@ public JdaTester() {
255274
return spy(new MemberImpl(guild, user));
256275
}
257276

277+
/**
278+
* Creates a Mockito spy for a text channel with the given channel id.
279+
* <p>
280+
* See {@link #getTextChannelSpy()} to get the default text channel used by this tester.
281+
*
282+
* @param channelId the id of the text channel to create
283+
* @return the created spy
284+
*/
285+
public @NotNull TextChannel createTextChannelSpy(long channelId) {
286+
return spy(new TextChannelImpl(channelId, guild));
287+
}
288+
258289
/**
259290
* Gets the Mockito mock used as universal reply action by all mocks created by this tester
260291
* instance.
@@ -294,6 +325,42 @@ public JdaTester() {
294325
return textChannel;
295326
}
296327

328+
/**
329+
* Gets the private channel spy used as universal private channel by all mocks created by this
330+
* tester instance.
331+
* <p>
332+
* For example {@link JDA#openPrivateChannelById(long)} will return this spy if used on the
333+
* instance returned by {@link #getJdaMock()}.
334+
*
335+
* @return the private channel spy used by this tester
336+
*/
337+
public @NotNull PrivateChannel getPrivateChannelSpy() {
338+
return privateChannel;
339+
}
340+
341+
/**
342+
* Gets the member spy used as universal member by all mocks created by this tester instance.
343+
* <p>
344+
* For example the events created by {@link #createSlashCommandInteractionEvent(SlashCommand)}
345+
* will return this spy on several of their methods.
346+
* <p>
347+
* See {@link #createMemberSpy(long)} to create other members.
348+
*
349+
* @return the member spy used by this tester
350+
*/
351+
public @NotNull Member getMemberSpy() {
352+
return member;
353+
}
354+
355+
/**
356+
* Gets the JDA mock used as universal instance by all mocks created by this tester instance.
357+
*
358+
* @return the JDA mock used by this tester
359+
*/
360+
public @NotNull JDA getJdaMock() {
361+
return jda;
362+
}
363+
297364
/**
298365
* Creates a mocked action that always succeeds and consumes the given object.
299366
* <p>
@@ -325,11 +392,25 @@ public JdaTester() {
325392
successConsumer.accept(t);
326393
return null;
327394
};
395+
Answer<RestAction<?>> mapExecution = invocation -> {
396+
Function<? super T, ?> mapFunction = invocation.getArgument(0);
397+
Object result = mapFunction.apply(t);
398+
return createSucceededActionMock(result);
399+
};
400+
Answer<RestAction<?>> flatMapExecution = invocation -> {
401+
Function<? super T, RestAction<?>> flatMapFunction = invocation.getArgument(0);
402+
return flatMapFunction.apply(t);
403+
};
328404

329405
doNothing().when(action).queue();
330406

331407
doAnswer(successExecution).when(action).queue(any());
332408
doAnswer(successExecution).when(action).queue(any(), any());
409+
when(action.onErrorMap(any())).thenReturn(action);
410+
when(action.onErrorMap(any(), any())).thenReturn(action);
411+
412+
doAnswer(mapExecution).when(action).map(any());
413+
doAnswer(flatMapExecution).when(action).flatMap(any());
333414

334415
return action;
335416
}
@@ -366,11 +447,27 @@ public JdaTester() {
366447
return null;
367448
};
368449

450+
Answer<RestAction<?>> errorMapExecution = invocation -> {
451+
Function<? super Throwable, ?> mapFunction = invocation.getArgument(0);
452+
Object result = mapFunction.apply(failureReason);
453+
return createSucceededActionMock(result);
454+
};
455+
456+
Answer<RestAction<?>> mapExecution = invocation -> createFailedActionMock(failureReason);
457+
Answer<RestAction<?>> flatMapExecution =
458+
invocation -> createFailedActionMock(failureReason);
459+
369460
doNothing().when(action).queue();
370461
doNothing().when(action).queue(any());
371462

463+
doAnswer(errorMapExecution).when(action).onErrorMap(any());
464+
doAnswer(errorMapExecution).when(action).onErrorMap(any(), any());
465+
372466
doAnswer(failureExecution).when(action).queue(any(), any());
373467

468+
doAnswer(mapExecution).when(action).map(any());
469+
doAnswer(flatMapExecution).when(action).flatMap(any());
470+
374471
return action;
375472
}
376473

application/src/test/java/org/togetherjava/tjbot/jda/SlashCommandInteractionEventBuilder.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5-
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
65
import net.dv8tion.jda.api.entities.Member;
76
import net.dv8tion.jda.api.entities.User;
7+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
88
import net.dv8tion.jda.api.interactions.commands.OptionType;
99
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
1010
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
@@ -103,6 +103,26 @@ public final class SlashCommandInteractionEventBuilder {
103103
return this;
104104
}
105105

106+
/**
107+
* Sets the given option, overriding an existing value under the same name.
108+
* <p>
109+
* If {@link #setSubcommand(String)} is set, this option will be interpreted as option to the
110+
* subcommand.
111+
* <p>
112+
* Use {@link #clearOptions()} to clear any set options.
113+
*
114+
* @param name the name of the option
115+
* @param value the value of the option
116+
* @return this builder instance for chaining
117+
* @throws IllegalArgumentException if the option does not exist in the corresponding command,
118+
* as specified by its {@link SlashCommand#getData()}
119+
*/
120+
public @NotNull SlashCommandInteractionEventBuilder setOption(@NotNull String name,
121+
long value) {
122+
putOptionRaw(name, value, OptionType.INTEGER);
123+
return this;
124+
}
125+
106126
/**
107127
* Sets the given option, overriding an existing value under the same name.
108128
* <p>
@@ -292,6 +312,14 @@ private SlashCommandInteractionEvent spySlashCommandEvent(String jsonData) {
292312
@NotNull OptionType type) {
293313
if (type == OptionType.STRING) {
294314
return (String) value;
315+
} else if (type == OptionType.INTEGER) {
316+
if (value instanceof Long asLong) {
317+
return value.toString();
318+
}
319+
320+
throw new IllegalArgumentException(
321+
"Expected a long, since the type was set to INTEGER. But got '%s'"
322+
.formatted(value.getClass()));
295323
} else if (type == OptionType.USER) {
296324
if (value instanceof User user) {
297325
return user.getId();

0 commit comments

Comments
 (0)