【问题标题】:How to mock (MOQ) IConfidentialClientApplication which has sealed setup AbstractAcquireTokenParameterBuilder?如何模拟(MOQ)已密封设置 AbstractAcquireTokenParameterBuilder 的 IConfidentialClientApplication?
【发布时间】:2020-12-17 03:23:37
【问题描述】:

我在尝试为 IConfidentialClientApplication 设置 moq 时遇到以下异常:

System.NotSupportedException:不支持的表达式:... => ....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 ...
}

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


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

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

【问题讨论】:

  • 能否包含从AcquireTokenForClient() 返回的类型的定义?似乎您需要为该类型创建一个单独的模拟/存根,并让它从ExecuteAsync()返回身份验证@
  • @devNull AcquireTokenForClient() 返回AcquireTokenForClientParameterBuilder。我更新了我的答案以更好地解释这一点。

标签: c# unit-testing .net-core moq msal


【解决方案1】:

看到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);
    }
}

这个例子显然是琐碎的,但仍然应该适用。界面只需要适合您的需求即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多