C# 模拟单元测试 GraphServiceClient

Dav*_*rin 7 c# moq xunit azure

我在使用 Moq 和 xUnit 在 C# 中编写单元测试时遇到问题。

在我的服务中,我有以下代码:

var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential);


return (await graphClient.Users.Request().Filter($"displayName eq '{mobilePhone}'").GetAsync()).FirstOrDefault();

Run Code Online (Sandbox Code Playgroud)

但我不知道模拟该函数的方法GraphClient

graphClient.Users.Request().Filter($"displayName eq '{mobilePhone}'").GetAsync()).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

Oli*_*ver 14

图 v5 SDK

将 Graph SDK 升级到 v5 后,模拟服务客户端的方式已发生变化,并且并没有变得更容易。有多种不同的方法可供选择,本期列出了其中的一些方法。我目前的模拟方法是这样的:

public static class RequestAdapterMockFactory
{
    public static Mock<IRequestAdapter> Create(MockBehavior mockBehavior = MockBehavior.Strict)
    {
        var mockSerializationWriterFactory = new Mock<ISerializationWriterFactory>();
        mockSerializationWriterFactory.Setup(factory => factory.GetSerializationWriter(It.IsAny<string>()))
            .Returns((string _) => new JsonSerializationWriter());

        var mockRequestAdapter = new Mock<IRequestAdapter>(mockBehavior);
        // The first path element must have four characters to mimic v1.0 or beta
        // This is especially needed to mock batch requests.
        mockRequestAdapter.SetupGet(adapter => adapter.BaseUrl).Returns("http://graph.test.internal/mock");
        mockRequestAdapter.SetupSet(adapter => adapter.BaseUrl = It.IsAny<string>());
        mockRequestAdapter.Setup(adapter => adapter.EnableBackingStore(It.IsAny<IBackingStoreFactory>()));
        mockRequestAdapter.SetupGet(adapter => adapter.SerializationWriterFactory).Returns(mockSerializationWriterFactory.Object);

        return mockRequestAdapter;
    }
}

var mockRequestAdapter = RequestAdapterMockFactory.Create();
var graphServiceClient = new GraphServiceClient(mockRequestAdapter.Object);

mockRequestAdapter.Setup(adapter => adapter.SendAsync(
    // Needs to be correct HTTP Method of the desired method 
    It.Is<RequestInformation>(info => info.HttpMethod == Method.GET),
    // Needs to be method from  object type that will be returned from the SDK method.
    Microsoft.Graph.Models.User.CreateFromDiscriminatorValue,
    It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new Microsoft.Graph.Models.User
    {
        DisplayName = "Hello World",
    });
Run Code Online (Sandbox Code Playgroud)

这种方法相当臃肿,但我没有找到适合我的情况的更好方法。在上面链接的 GitHub 问题中,您可以找到可能更适合您的其他方法。

访问和使用ReturnsAsync()中的值

这可以通过使用采用相应Func<>的方法的重载来实现。在我们的例子中,要访问组请求所需的 id 并返回具有该 id 的组,您可以尝试以下操作:

mockRequestAdapter.Setup(adapter => adapter.SendAsync(
    // Needs to be correct HTTP Method of the desired method 
    It.Is<RequestInformation>(info => info.HttpMethod == Method.GET),
    //  Needs to be method from object type that will be returned from the SDK method.
    Group.CreateFromDiscriminatorValue,
    It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync((
        RequestInformation info,
        // Needs to be the  same type as above!
        ParsableFactory<Group> _,
        Dictionary<string, ParsableFactory<IParsable>> _,
        CancellationToken _) =>
    {
        var id = (string)info.PathParameters["group%2Did"];
        return new Group { Id = id };
    });
Run Code Online (Sandbox Code Playgroud)

无论您现在要请求哪个组,您都将使用所需的 id 取回它:

var id = Guid.NewGuid().ToString();
var group = await graphServiceClient.Groups[id].GetAsync();
Run Code Online (Sandbox Code Playgroud)

图 v4 SDK

根据您的用例和现有代码库,您还可以在构造函数调用中为两个接口提供一些空存根,并使用重写虚拟函数的功能。如果您使用文档中提供的一些模拟框架(例如 Moq),这会派上用场:

// Arrange
var mockAuthProvider = new Mock<IAuthenticationProvider>();
var mockHttpProvider = new Mock<IHttpProvider>();
var mockGraphClient = new Mock<GraphServiceClient>(mockAuthProvider.Object, mockHttpProvider.Object);

ManagedDevice md = new ManagedDevice
{
    Id = "1",
    DeviceCategory = new DeviceCategory()
    {
        Description = "Sample Description"
    }
};

// setup the call
mockGraphClient
    .Setup(g => g.DeviceManagement.ManagedDevices["1"]
        .Request()
        .GetAsync(CancellationToken.None))
        .ReturnsAsync(md)
        .Verifiable();

// Act
var graphClient = mockGraphClient.Object;
var device = await graphClient.DeviceManagement.ManagedDevices["1"]
    .Request()
    .GetAsync(CancellationToken.None);

// Assert
Assert.Equal("1",device.Id);
Run Code Online (Sandbox Code Playgroud)

通过使用这种方法,您不必费心处理在线完成的具体 HTTP 请求。相反,您可以简单地覆盖(嵌套)方法调用及其参数,并定义返回的对象,而无需序列化/反序列化步骤。另请注意,在模拟中,您可以使用例如It.IsAny<string>()和类似的构造来定义是否需要精确的参数检查或其他内容。