I would like to unit test one method in derived ModuleBase<SocketCommandContext>
class.
Is there a good way to hijack property ModuleBase.Context ?
One option might be overriding it to auto-property
and assign it with fake instance of derived SocketCommandContext
class. The same thing might be needed for SocketCommandContext.User
member.
Perhaps there is better way.
I couldn't find anything useful in project page
Thanks.
public class SampleModule: ModuleBase<SocketCommandContext>
{
private readonly IRepository _repository;
public SocketCommandContext Context { get; set; }
public SampleModule(
IRepository _repository)
{
_repository= repository;
}
[Command("test", RunMode = RunMode.Async)]
public async Task RunAsync([Summary("user")]SocketGuildUser? targetUser = null)
{
var user = targetUser ?? Context.User as SocketGuildUser ?? throw new Exception();
var result = await _repository.GetAsync(user.Id);
await ReplyAsync(result.Value);
}
}
private readonly Mock<IRepository> _repositoryMock = new(MockBehavior.Strict);
[TestMethod]
public async Task Should_Reply()
{
_repositoryMock
.Setup(pr => pr.GetAsync(It.IsAny<ulong>()))
.Verifiable();
var module = new SampleModule(_repositoryMock.Object);
await module.RunAsync();
_repositoryMock.Verify();
}
It is possible to mock ModuleBase.Context
by invoking via reflection internal method IModuleBase.SetContext(ICommandContext)
with assigns new context from parameter.
private void SetContext(SampleModule module)
{
var setContext = module.GetType().GetMethod(
"Discord.Commands.IModuleBase.SetContext",
BindingFlags.NonPublic | BindingFlags.Instance);
setContext.Invoke(_module, new object[] { _commandContextMock.Object });
}
As for SocketGuildUser
, one can use internal ctor
which requires SocketGuild
and SocketGlobalUser
. They also need to be instantiated with reflection and need other dependencies.
SocketGlobalUser
was a tough one as the class definition is even internal.
Another option is to switch Socket*
types to interfaces which they implement.
Like SocketGuildUser
would become IGuildUser
or IUser
.
Example methods which instantiate Socket types
public static object CreateSocketGlobalUser(DiscordSocketClient discordSocketClient, ulong id)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var discordNetAssembly = assemblies
.FirstOrDefault(pr => pr.FullName == "Discord.Net.WebSocket, Version=2.4.0.0, Culture=neutral, PublicKeyToken=null");
var socketGlobalUserType = discordNetAssembly.GetType("Discord.WebSocket.SocketGlobalUser");
var socketGlobalUserCtor = socketGlobalUserType.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, new[]{
typeof(DiscordSocketClient),
typeof(ulong),
}, null);
var parameters = new object[] {
discordSocketClient, id
};
var socketGlobalUser = socketGlobalUserCtor.Invoke(parameters);
return socketGlobalUser;
}
public static SocketGuild CreateSocketGuild(DiscordSocketClient discordSocketClient, ulong id)
{
var bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
var socketGuildCtor = typeof(SocketGuild).GetConstructor(
bindingAttr,
null, new[]{
typeof(DiscordSocketClient),
typeof(ulong),
}, null);
var socketGuild = (SocketGuild)socketGuildCtor.Invoke(new object[] {
discordSocketClient, id,
});
return socketGuild;
}
public static SocketGuildUser CreateSocketGuildUser(SocketGuild socketGuild, object socketGlobalUser)
{
var bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
var types = new[]{
typeof(SocketGuild),
socketGlobalUser.GetType(),
};
var socketGuildUserCtor = typeof(SocketGuildUser).GetConstructor(
bindingAttr,
null, types, null);
var parameters = new object[] {
socketGuild, socketGlobalUser
};
var socketGuildUser = (SocketGuildUser)socketGuildUserCtor.Invoke(parameters);
return socketGuildUser;
}
ReplyAsync
method internally calls Context.Channel.SendMessageAsync
and with a bit of work it can also be mocked.
private readonly Mock<ICommandContext> _commandContextMock = new(MockBehavior.Strict);
private readonly Mock<IMessageChannel> _messageChannelMock = new(MockBehavior.Strict);
private readonly Mock<IUserMessage> _userMessageMock = new(MockBehavior.Strict);
_commandContextMock
.Setup(pr => pr.Channel)
.Returns(_messageChannelMock.Object);
_messageChannelMock
.Setup(pr => pr.SendMessageAsync(
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<Embed>(),
It.IsAny<RequestOptions>(),
It.IsAny<AllowedMentions>(),
It.IsAny<MessageReference>()))
.ReturnsAsync(_userMessageMock.Object);
The example test with things above.
private readonly Mock<ICommandContext> _commandContextMock = new(MockBehavior.Strict);
private readonly Mock<IMessageChannel> _messageChannelMock = new(MockBehavior.Strict);
private readonly Mock<IUserMessage> _userMessageMock = new(MockBehavior.Strict);
private readonly Mock<IRepository> _repositoryMock = new(MockBehavior.Strict);
[TestMethod]
public async Task Should_Reply()
{
_repositoryMock
.Setup(pr => pr.GetAsync(It.IsAny<ulong>()))
.Verifiable();
var discordSocketClientMock = new Mock<DiscordSocketClient>(MockBehavior.Strict);
var socketGlobalUser = CreateSocketGlobalUser(discordSocketClientMock.Object, 1);
var socketGuild = CreateSocketGuild(discordSocketClientMock.Object, 1);
var socketGuildUser = CreateSocketGuildUser(socketGuild, socketGlobalUser);
_commandContextMock
.Setup(pr => pr.Channel)
.Returns(_messageChannelMock.Object);
_commandContextMock
.Setup(pr => pr.User)
.Returns(socketGuildUser);
_messageChannelMock
.Setup(pr => pr.SendMessageAsync(
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<Embed>(),
It.IsAny<RequestOptions>(),
It.IsAny<AllowedMentions>(),
It.IsAny<MessageReference>()))
.ReturnsAsync(_userMessageMock.Object);
var module = new SampleModule(_repositoryMock.Object);
SetContext(_commandContextMock.Object);
await module.RunAsync();
_repositoryMock.Verify();
}
Some parts could be extracted to test base class given by how big the setup code is.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.