如何模拟(最小起订量)IConfidentialClientApplication 已密封设置 AbstractAcquireTokenParameterBuilder?
How to mock (MOQ) IConfidentialClientApplication which has sealed setup AbstractAcquireTokenParameterBuilder?
我在尝试为 IConfidentialClientApplication
设置 moq 时遇到以下异常:
System.NotSupportedException : Unsupported expression: ... =>
....ExecuteAsync() Non-overridable members (here:
AbstractAcquireTokenParameterBuilder.ExecuteAsync)
may not be used in setup / verification expressions.
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 ...
}
_.AcquireTokenForClient
返回一个AcquireTokenForClientParameterBuilder
; “使您能够在执行令牌请求之前添加可选参数的构建器”。 This is a sealed
class,所以我不能轻易地模拟这个棘手的对象。
对于那些好奇的人来说,CreateAuthenticationResult()
是一种从 Microsoft.Identity.Client.AuthenticationResult
调用签名的方法,微软专门添加了该签名用于存根 AuthenticationResult
,因为它不能被模拟,因为它也是密封的class.
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/682
看到 AcquireTokenForClientParameterBuilder
是通过外部库提供的,您显然无法修改它以使其更易于测试。鉴于此,我建议在您自己的界面后面抽象该代码(出于测试目的应用适配器模式)。
以下面的 service/test 为例,说明您当前如何使用 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);
}
通过引入一个单独的接口,您的代码可以简单地依赖于它,让您可以控制它的方式 used/tested:
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);
}
}
这个例子显然是微不足道的,但应该仍然适用。该界面只需要适合您的需求。
我在尝试为 IConfidentialClientApplication
设置 moq 时遇到以下异常:
System.NotSupportedException : Unsupported expression: ... => ....ExecuteAsync() Non-overridable members (here: AbstractAcquireTokenParameterBuilder.ExecuteAsync) may not be used in setup / verification expressions.
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 ...
}
_.AcquireTokenForClient
返回一个AcquireTokenForClientParameterBuilder
; “使您能够在执行令牌请求之前添加可选参数的构建器”。 This is a sealed
class,所以我不能轻易地模拟这个棘手的对象。
对于那些好奇的人来说,CreateAuthenticationResult()
是一种从 Microsoft.Identity.Client.AuthenticationResult
调用签名的方法,微软专门添加了该签名用于存根 AuthenticationResult
,因为它不能被模拟,因为它也是密封的class.
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/682
看到 AcquireTokenForClientParameterBuilder
是通过外部库提供的,您显然无法修改它以使其更易于测试。鉴于此,我建议在您自己的界面后面抽象该代码(出于测试目的应用适配器模式)。
以下面的 service/test 为例,说明您当前如何使用 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);
}
通过引入一个单独的接口,您的代码可以简单地依赖于它,让您可以控制它的方式 used/tested:
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);
}
}
这个例子显然是微不足道的,但应该仍然适用。该界面只需要适合您的需求。