首页 > 解决方案 > 如何模拟(MOQ)已密封设置 AbstractAcquireTokenParameterBuilder 的 IConfidentialClientApplication?

问题描述

尝试设置最小起订量时出现以下异常IConfidentialClientApplication

System.NotSupportedException : Unsupported expression: ... => ....ExecuteAsync() 不可覆盖的成员(此处:AbstractAcquireTokenParameterBuilder.ExecuteAsync)不得用于设置/验证表达式。

private Mock<IConfidentialClientApplication> _appMock = new Mock<IConfidentialClientApplication>();

[Fact]
public async Task GetAccessTokenResultAsync_WithGoodSetup_ReturnsToken()
{
    // Leverages MSAL AuthenticationResult constructor meant for mocks in test
    var authentication = CreateAuthenticationResult();

    // EXCEPTION THROWN HERE
    _appMock.Setup(_ => _.AcquireTokenForClient(It.IsAny<string[]>()).ExecuteAsync())
        .ReturnsAsync(authentication);

    ... rest of test ...
}

AnAcquireTokenForClientParameterBuilder_.AcquireTokenForClient;返回 “使您能够在执行令牌请求之前添加可选参数的构建器”。这是一个sealed,所以我不能轻易模拟这个棘手的对象。


对于那些好奇的人来说,这CreateAuthenticationResult()是一种调用签名的方法,该方法是Microsoft.Identity.Client.AuthenticationResultMicrosoft 专门为存根添加的AuthenticationResult,因为它也是一个密封类,因此无法模拟。

https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/682

标签: c#unit-testing.net-coremoqmsal

解决方案


看到这AcquireTokenForClientParameterBuilder是通过外部库提供的,您显然无法对其进行修改以使其更具可测试性。鉴于此,我建议在您自己的接口后面抽象出该代码(出于测试目的应用适配器模式)。

以以下服务/测试为例,说明您当前如何使用IConfidentialClientApplication并尝试模拟它(这会导致您看到相同的错误):

public class MyService
{
    private readonly IConfidentialClientApplication _confidentialClientApplication;

    public MyService(IConfidentialClientApplication confidentialClientApplication)
    {
        _confidentialClientApplication = confidentialClientApplication;
    }

    public async Task<string> GetAccessToken(IEnumerable<string> scopes)
    {
        AcquireTokenForClientParameterBuilder tokenBuilder = _confidentialClientApplication.AcquireTokenForClient(scopes);
        AuthenticationResult token = await tokenBuilder.ExecuteAsync();
        return token.AccessToken;
    }
}

public class UnitTest1
{
    [Fact]
    public async Task Test1()
    {
        Mock<IConfidentialClientApplication> _appMock = new Mock<IConfidentialClientApplication>();
        AuthenticationResult authentication = CreateAuthenticationResult("myToken");
        _appMock
            .Setup(_ => _.AcquireTokenForClient(It.IsAny<string[]>()).ExecuteAsync())
            .ReturnsAsync(authentication);

        var myService = new MyService(_appMock.Object);
        string accessToken = await myService.GetAccessToken(new string[] { });

        Assert.Equal("myToken", accessToken);
    }

    private AuthenticationResult CreateAuthenticationResult(string accessToken) => 
        new AuthenticationResult(accessToken, true, null, DateTimeOffset.Now, DateTimeOffset.Now, string.Empty, null, null, null, Guid.Empty);
}

通过引入一个单独的接口,您的代码可以简单地依赖它,让您控制它的使用/测试方式:

public interface IIdentityClientAdapter
{
    Task<string> GetAccessToken(IEnumerable<string> scopes);
}

public class IdentityClientAdapter : IIdentityClientAdapter
{
    private readonly IConfidentialClientApplication _confidentialClientApplication;

    public IdentityClientAdapter(IConfidentialClientApplication confidentialClientApplication)
    {
        _confidentialClientApplication = confidentialClientApplication;
    }

    public async Task<string> GetAccessToken(IEnumerable<string> scopes)
    {
        AcquireTokenForClientParameterBuilder tokenBuilder = _confidentialClientApplication.AcquireTokenForClient(scopes);
        AuthenticationResult token = await tokenBuilder.ExecuteAsync();
        return token.AccessToken;
    }
}

public class MyService
{
    private readonly IIdentityClientAdapter _identityClientAdapter;

    public MyService(IIdentityClientAdapter identityClientAdapter)
    {
        _identityClientAdapter = identityClientAdapter;
    }

    public async Task<string> GetAccessToken(IEnumerable<string> scopes)
    {
        return await _identityClientAdapter.GetAccessToken(scopes);
    }
}

public class UnitTest1
{
    [Fact]
    public async Task Test1()
    {
        Mock<IIdentityClientAdapter> _appMock = new Mock<IIdentityClientAdapter>();
        _appMock
            .Setup(_ => _.GetAccessToken(It.IsAny<string[]>()))
            .ReturnsAsync("myToken");

        var myService = new MyService(_appMock.Object);
        string accessToken = await myService.GetAccessToken(new string[] { });

        Assert.Equal("myToken", accessToken);
    }
}

这个例子显然是微不足道的,但仍然应该适用。该界面只需要适合您的需求。


推荐阅读