可以通过反射内部方法调用IModuleBase.SetContext(ICommandContext) 来模拟ModuleBase.Context,并从参数中分配新的上下文。
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 });
}
对于SocketGuildUser,可以使用内部ctor,这需要SocketGuild和SocketGlobalUser。它们还需要通过反射进行实例化,并且需要其他依赖项。
SocketGlobalUser 是一个棘手的问题,因为类定义甚至是内部的。
另一种选择是将Socket* 类型切换到它们实现的接口。
就像SocketGuildUser 会变成IGuildUser 或IUser。
实例化 Socket 类型的示例方法
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 方法在内部调用Context.Channel.SendMessageAsync 并且通过一些工作也可以模拟它。
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);
上面的例子测试。
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();
}
根据设置代码的大小,可以提取一些部分来测试基类。