单元测试中如何Mock SslStream?Mock AuthenticateAsClientAsync方法及安全属性并覆盖异常分支
用Moq测试SslStream认证方法的全分支覆盖与异常触发
你已经选对了方向——因为SslStream的AuthenticateAsClientAsync和相关安全属性都是virtual/override,所以完全可以用Moq来模拟这些行为,进而覆盖AuthenticateSSLStream方法的所有条件分支。下面我会一步步拆解每个分支的测试方案,以及如何触发认证异常。
测试前置准备
首先,我们需要准备测试依赖:
- 一个测试用的
X509Certificate2(可以用自签证书,测试场景下真实证书比Mock更稳妥) - Moq创建的
SslStream实例 - 测试框架(这里用xUnit举例,NUnit逻辑完全一致)
假设你的类定义如下:
public class SslAuthenticator { private readonly X509Certificate2 myCertificate; public SslAuthenticator(X509Certificate2 certificate) { myCertificate = certificate; } internal bool AuthenticateSSLStream(SslStream sslStream) { string certName = myCertificate.GetNameInfo(X509NameType.SimpleName, false); bool isAuthenticationCompleted = sslStream.AuthenticateAsClientAsync(certName, new X509Certificate2Collection(myCertificate), SslProtocols.Tls11 | SslProtocols.Tls12, true).Wait(10000); if (!isAuthenticationCompleted || !sslStream.IsAuthenticated || !sslStream.IsEncrypted || !sslStream.IsSigned) { throw new AuthenticationException("Authentication is not succeeded!"); } return isAuthenticationCompleted; } }
1. 覆盖所有条件分支
我们需要针对每个失败条件和成功条件编写单独的测试用例:
分支1:认证成功(所有条件满足)
这是方法的正常执行路径,需要模拟异步认证完成,且三个安全属性均为true:
[Fact] public void AuthenticateSSLStream_WhenAllConditionsMet_ReturnsTrue() { // Arrange var testCert = new X509Certificate2("test-cert.pfx", "test-password"); // 替换为你的测试证书 var authenticator = new SslAuthenticator(testCert); var mockSslStream = new Mock<SslStream>(); // 模拟异步认证立即完成 mockSslStream.Setup(s => s.AuthenticateAsClientAsync( It.IsAny<string>(), It.IsAny<X509Certificate2Collection>(), It.IsAny<SslProtocols>(), It.IsAny<bool>())) .Returns(Task.CompletedTask); // 设置安全属性为true mockSslStream.SetupGet(s => s.IsAuthenticated).Returns(true); mockSslStream.SetupGet(s => s.IsEncrypted).Returns(true); mockSslStream.SetupGet(s => s.IsSigned).Returns(true); // Act var result = authenticator.AuthenticateSSLStream(mockSslStream.Object); // Assert Assert.True(result); }
分支2:认证超时(Wait返回false)
模拟异步认证未在10秒内完成,触发异常:
[Fact] public void AuthenticateSSLStream_WhenAuthenticationTimesOut_ThrowsAuthenticationException() { // Arrange var testCert = new X509Certificate2("test-cert.pfx", "test-password"); var authenticator = new SslAuthenticator(testCert); var mockSslStream = new Mock<SslStream>(); // 模拟一个永远不会完成的Task,让Wait返回false var neverCompletedTask = new TaskCompletionSource<bool>().Task; mockSslStream.Setup(s => s.AuthenticateAsClientAsync( It.IsAny<string>(), It.IsAny<X509Certificate2Collection>(), It.IsAny<SslProtocols>(), It.IsAny<bool>())) .Returns(neverCompletedTask); // Act & Assert Assert.Throws<AuthenticationException>(() => authenticator.AuthenticateSSLStream(mockSslStream.Object)); }
分支3:认证完成但IsAuthenticated为false
模拟认证完成,但身份验证状态不满足:
[Fact] public void AuthenticateSSLStream_WhenIsAuthenticatedFalse_ThrowsAuthenticationException() { // Arrange var testCert = new X509Certificate2("test-cert.pfx", "test-password"); var authenticator = new SslAuthenticator(testCert); var mockSslStream = new Mock<SslStream>(); mockSslStream.Setup(s => s.AuthenticateAsClientAsync(It.IsAny<string>(), It.IsAny<X509Certificate2Collection>(), It.IsAny<SslProtocols>(), It.IsAny<bool>())) .Returns(Task.CompletedTask); mockSslStream.SetupGet(s => s.IsAuthenticated).Returns(false); mockSslStream.SetupGet(s => s.IsEncrypted).Returns(true); mockSslStream.SetupGet(s => s.IsSigned).Returns(true); // Act & Assert Assert.Throws<AuthenticationException>(() => authenticator.AuthenticateSSLStream(mockSslStream.Object)); }
分支4:认证完成但IsEncrypted为false
仅修改加密状态为false,触发异常:
[Fact] public void AuthenticateSSLStream_WhenIsEncryptedFalse_ThrowsAuthenticationException() { // Arrange var testCert = new X509Certificate2("test-cert.pfx", "test-password"); var authenticator = new SslAuthenticator(testCert); var mockSslStream = new Mock<SslStream>(); mockSslStream.Setup(s => s.AuthenticateAsClientAsync(It.IsAny<string>(), It.IsAny<X509Certificate2Collection>(), It.IsAny<SslProtocols>(), It.IsAny<bool>())) .Returns(Task.CompletedTask); mockSslStream.SetupGet(s => s.IsAuthenticated).Returns(true); mockSslStream.SetupGet(s => s.IsEncrypted).Returns(false); mockSslStream.SetupGet(s => s.IsSigned).Returns(true); // Act & Assert Assert.Throws<AuthenticationException>(() => authenticator.AuthenticateSSLStream(mockSslStream.Object)); }
分支5:认证完成但IsSigned为false
仅修改签名状态为false,触发异常:
[Fact] public void AuthenticateSSLStream_WhenIsSignedFalse_ThrowsAuthenticationException() { // Arrange var testCert = new X509Certificate2("test-cert.pfx", "test-password"); var authenticator = new SslAuthenticator(testCert); var mockSslStream = new Mock<SslStream>(); mockSslStream.Setup(s => s.AuthenticateAsClientAsync(It.IsAny<string>(), It.IsAny<X509Certificate2Collection>(), It.IsAny<SslProtocols>(), It.IsAny<bool>())) .Returns(Task.CompletedTask); mockSslStream.SetupGet(s => s.IsAuthenticated).Returns(true); mockSslStream.SetupGet(s => s.IsEncrypted).Returns(true); mockSslStream.SetupGet(s => s.IsSigned).Returns(false); // Act & Assert Assert.Throws<AuthenticationException>(() => authenticator.AuthenticateSSLStream(mockSslStream.Object)); }
2. 触发认证异常的其他方式
除了上述条件分支,你还可以模拟AuthenticateAsClientAsync本身抛出异常(比如证书无效、协议不支持等场景):
[Fact] public void AuthenticateSSLStream_WhenAuthenticateThrowsException_ThrowsAuthenticationException() { // Arrange var testCert = new X509Certificate2("test-cert.pfx", "test-password"); var authenticator = new SslAuthenticator(testCert); var mockSslStream = new Mock<SslStream>(); // 模拟认证方法直接抛出异常 mockSslStream.Setup(s => s.AuthenticateAsClientAsync(It.IsAny<string>(), It.IsAny<X509Certificate2Collection>(), It.IsAny<SslProtocols>(), It.IsAny<bool>())) .ThrowsAsync(new InvalidOperationException("Invalid certificate provided")); // Act & Assert Assert.Throws<AuthenticationException>(() => authenticator.AuthenticateSSLStream(mockSslStream.Object)); }
这里用ThrowsAsync模拟异步方法抛出异常,Wait调用会捕获到这个异常,进而进入你的条件判断逻辑,最终抛出AuthenticationException。
内容的提问来源于stack exchange,提问作者Péter Szilvási
相关产品推荐
相关产品推荐

