如何模拟 Microsoft Graph API SDK 客户端?

vis*_*ale 7 c# moq asp.net-web-api microsoft-graph-api

我在我的项目中使用了 Microsoft Graph SDK 来调用图形 API,为此我需要使用 GraphServiceClient。要使用 GraphServiceClient,我必须添加一些辅助类,其中 SDKHelper 是具有 GetAuthenticatedClient() 方法的静态类。由于被测方法与静态的 SDKHelper 紧密耦合,因此我创建了一个服务类并注入了依赖项。

下面是控制器和方法,

public class MyController
{
    private IMyServices _iMyServices { get; set; }

    public UserController(IMyServices iMyServices)
    {
        _iMyServices = iMyServices;
    }
    public async Task<HttpResponseMessage> GetGroupMembers([FromUri]string groupID)
    {
        GraphServiceClient graphClient = _iMyServices.GetAuthenticatedClient();
        IGroupMembersCollectionWithReferencesPage groupMembers = await _iMyServices.GetGroupMembersCollectionWithReferencePage(graphClient, groupID);
        return this.Request.CreateResponse(HttpStatusCode.OK, groupMembers, "application/json");
    }
}
Run Code Online (Sandbox Code Playgroud)

服务类,

public class MyServices : IMyServices
{
    public GraphServiceClient GetAuthenticatedClient()
    {
        GraphServiceClient graphClient = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                async (requestMessage) =>
                {
                    string accessToken = await SampleAuthProvider.Instance.GetAccessTokenAsync();
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
                    requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
                }));
        return graphClient;
    }

    public async Task<IGraphServiceGroupsCollectionPage> GetGraphServiceGroupCollectionPage(GraphServiceClient graphClient)
    {
        return await graphClient.Groups.Request().GetAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

我在为上述服务类方法编写单元测试用例时遇到了挑战,以下是我的单元测试代码:

public async Task GetGroupMembersCollectionWithReferencePage_Success()
{
    GraphServiceClient graphClient = GetAuthenticatedClient();
    IGraphServiceGroupsCollectionPage groupMembers = await graphClient.Groups.Request().GetAsync();

    Mock<IUserServices> mockIUserService = new Mock<IUserServices>();
    IGraphServiceGroupsCollectionPage expectedResult = await mockIUserService.Object.GetGraphServiceGroupCollectionPage(graphClient);
    Assert.AreEqual(expectedResult, groupMembers);
}
Run Code Online (Sandbox Code Playgroud)

在上面的测试用例中,第 4 行引发异常 - 消息:“Connect3W.UserMgt.Api.Helpers.SampleAuthProvider”的类型初始值设定项引发异常。内部异常消息:值不能为空。参数名称:格式

谁能建议我如何使用 MOQ 来模拟上面的代码或任何其他方法来完成测试用例?

Nko*_*osi 9

不要嘲笑你不拥有的东西。GraphServiceClient应被视为第 3 方依赖项,并应封装在您控制的抽象之后

您尝试这样做,但仍在泄漏实现问题。

该服务可以简化为

public interface IUserServices {

    Task<IGroupMembersCollectionWithReferencesPage> GetGroupMembers(string groupID);

}
Run Code Online (Sandbox Code Playgroud)

和实施

public class UserServices : IUserServices {
    GraphServiceClient GetAuthenticatedClient() {
        var graphClient = new GraphServiceClient(
            new DelegateAuthenticationProvider(
                async (requestMessage) =>
                {
                    string accessToken = await SampleAuthProvider.Instance.GetAccessTokenAsync();
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
                    requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
                }));
        return graphClient;
    }

    public Task<IGroupMembersCollectionWithReferencesPage> GetGroupMembers(string groupID) {
        var graphClient = GetAuthenticatedClient();
        return graphClient.Groups[groupID].Members.Request().GetAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

这也会导致控制器被简化

public class UserController : ApiController {
    private readonly IUserServices service;

    public UserController(IUserServices myServices) {
        this.service = myServices;
    }

    public async Task<IHttpActionResult> GetGroupMembers([FromUri]string groupID) {
        IGroupMembersCollectionWithReferencesPage groupMembers = await service.GetGroupMembers(groupID);
        return Ok(groupMembers);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在为了测试控制器,您可以轻松地模拟抽象以按预期运行,以便完成测试,因为控制器与第GraphServiceClient3 方依赖项完全分离,并且可以单独测试控制器。

[TestClass]
public class UserControllerShould {
    [TestMethod]
    public async Task GetGroupMembersCollectionWithReferencePage_Success() {
        //Arrange
        var groupId = "12345";
        var expectedResult = Mock.Of<IGroupMembersCollectionWithReferencesPage>();
        var mockService = new Mock<IUserServices>();
        mockService
            .Setup(_ => _.GetGroupMembers(groupId))
            .ReturnsAsync(expectedResult);

        var controller = new UserController(mockService.Object);

        //Act
        var result = await controller.GetGroupMembers(groupId) as System.Web.Http.Results.OkNegotiatedContentResult<IGroupMembersCollectionWithReferencesPage>;

        //Assert
        Assert.IsNotNull(result);
        var actualResult = result.Content;
        Assert.AreEqual(expectedResult, actualResult);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是 TDD 如何推动开发人员寻找更好的抽象的一个很好的例子。如果不容易模拟,则抽象是错误的。在这种情况下,将调用代码分解到它自己的层中似乎很自然。 (3认同)