c# - 如何模拟(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.AuthenticationResult
Microsoft 专门为存根添加的AuthenticationResult
,因为它也是一个密封类,因此无法模拟。
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/682
解决方案
看到这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);
}
}
这个例子显然是微不足道的,但仍然应该适用。该界面只需要适合您的需求。
推荐阅读
- html - 始终将粘性定位在视口底部
- c++ - 为 STL 容器预分配内存的自定义分配器
- amazon-web-services - AWS 服务和 AWS 市场之间有什么区别?
- c# - Microsoft.Data AsNoTracking() 用于跟踪 SQL 视图更新
- python - 为什么在使用 for 循环将键/值插入我构建的哈希表时 Python 打印内存位置而不是值
- python - 如何从列表中的后一个元素开始选择列表中的元素到python中的前一个元素
- python - 用字典值解密歌曲
- java - SpringBoot:实体 json 序列化与继承
- python - 在 discord.py 中重置命令的冷却时间
- c# - 二维激光雷达扫描中的集群比较