【单元测试篇】.Net单元测试
目录
3.3、Assert类常用方法(NUnit.Framework Version 3.12.0.0)
4.2.5、Mock的SetupProperty与SetupAllProperties
单元测试的特点?
集成测试的特点?
using NUnit.Framework;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
namespace XUnitDemo.NUnitTests.AOP
{
public class LogTestActionAttribute : TestActionAttribute
{
public override void BeforeTest(ITest test)
{
System.Diagnostics.Debug.WriteLine($"{nameof(BeforeTest)}-ClassName:{test.ClassName};Fixture:{test.Fixture};FullName:{test.FullName};MethodName:{test.MethodName};Name:{test.Name}");
base.BeforeTest(test);
}
public override void AfterTest(ITest test)
{
System.Diagnostics.Debug.WriteLine($"{nameof(AfterTest)}-ClassName:{test.ClassName};Fixture:{test.Fixture};FullName:{test.FullName};MethodName:{test.MethodName};Name:{test.Name}");
base.AfterTest(test);
}
}
}
using NUnit.Framework;
using XUnitDemo.NUnitTests.AOP;
namespace XUnitDemo.Tests
{
[TestFixture]
public class AccountServiceUnitTest
{
[LogTestAction]
[Test]
[Category("*用户名认证的测试*")]
public void Auth_UserNamePwd_ReturnTrue()
{
Assert.Pass();
}
[Test]
[Category("*邮箱认证的测试*")]
public void Auth_EmailPwd_ReturnTrue()
{
Assert.Pass();
}
[Test]
[Category("*手机号认证的测试*")]
public void Auth_MobilePwd_ReturnTrue()
{
Assert.Pass();
}
[Test]
[Category("*手机号认证的测试*")]
public void Auth_MobileCode_ReturnTrue()
{
Assert.Pass();
}
}
}
//BeforeTest-ClassName:XUnitDemo.Tests.AccountCountrollerUnitTest;Fixture:XUnitDemo.Tests.AccountCountrollerUnitTest;FullName:XUnitDemo.Tests.AccountCountrollerUnitTest.Auth_UserNamePwd_ReturnTrue;MethodName:Auth_UserNamePwd_ReturnTrue;Name:Auth_UserNamePwd_ReturnTrue
//AfterTest-ClassName:XUnitDemo.Tests.AccountCountrollerUnitTest;Fixture:XUnitDemo.Tests.AccountCountrollerUnitTest;FullName:XUnitDemo.Tests.AccountCountrollerUnitTest.Auth_UserNamePwd_ReturnTrue;MethodName:Auth_UserNamePwd_ReturnTrue;Name:Auth_UserNamePwd_ReturnTrue
using NUnit.Framework;
using XUnitDemo.NUnitTests.AOP;
[assembly: Category("NUnit相关的测试")]
namespace XUnitDemo.Tests
{
[TestFixture]
[Category("*账号相关的测试*")]
public class AccountServiceUnitTest
{
public void SetUp()
{ }
[LogTestAction]
[Test]
[Category("*用户名认证的测试*")]
public void Auth_UserNamePwd_ReturnTrue()
{
Assert.Pass();
}
[Test]
[Category("*邮箱认证的测试*")]
public void Auth_EmailPwd_ReturnTrue()
{
Assert.Pass();
}
[Test]
[Category("*手机号认证的测试*")]
public void Auth_MobilePwd_ReturnTrue()
{
Assert.Pass();
}
[Test]
[Category("*手机号认证的测试*")]
public void Auth_MobileCode_ReturnTrue()
{
Assert.Pass();
}
}
}
using NUnit.Framework;
namespace XUnitDemo.NUnitTests.Product
{
[SetUpFixture]
public class SettingProductUnitTest
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
System.Diagnostics.Debug.WriteLine($"{nameof(SettingProductUnitTest)}-{nameof(OneTimeSetUp)}");
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
System.Diagnostics.Debug.WriteLine($"{nameof(SettingProductUnitTest)}-{nameof(OneTimeTearDown)}");
}
}
}
using NUnit.Framework;
namespace XUnitDemo.NUnitTests.User
{
[SetUpFixture]
public class SettingUserUnitTest
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
System.Diagnostics.Debug.WriteLine($"{nameof(SettingUserUnitTest)}-{nameof(OneTimeSetUp)}");
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
System.Diagnostics.Debug.WriteLine($"{nameof(SettingUserUnitTest)}-{nameof(OneTimeTearDown)}");
}
}
}
3.2、编写测试的步骤:
3.3、Assert类常用方法(NUnit.Framework Version 3.12.0.0):
Assert.AreEqual |
|
Assert.AreNotEqual |
|
Assert.AreNotSame |
|
Assert.AreSame |
|
Assert.ByVal |
|
Assert.Catch |
|
Assert.Contains |
|
Assert.DoesNotThrow |
|
Assert.False |
|
Assert.Fail |
|
Assert.Greater |
|
Assert.GreaterOrEqual |
|
Assert.Ignore |
|
Assert.Inconclusive |
|
Assert.IsAssignableFrom |
|
Assert.IsEmpty |
|
Assert.IsFalse |
|
Assert.IsInstanceOf |
|
Assert.IsNaN |
|
Assert.IsNotAssignableFrom |
|
Assert.IsNotEmpty |
|
Assert.IsNotInstanceOf |
|
Assert.IsNotNull |
|
Assert.IsNull |
|
Assert.IsTrue |
|
Assert.Less |
|
Assert.LessOrEqual |
|
Assert.Multiple |
|
Assert.Negative |
|
Assert.NotNull |
|
Assert.NotZero |
|
Assert.Null |
|
Assert.Pass |
|
Assert.Positive |
|
Assert.ReferenceEquals |
|
Assert.Throws |
|
Assert.That |
|
Assert.True |
|
Assert.Warn |
|
Assert.Zero |
|
3.4.1、外部依赖
3.4.
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using XUnitDemo.IService;
namespace XUnitDemo.WebApi.Controller
{
[Route("api/[controller]")]
[ApiController]
public class BlogController : ControllerBase
{
private readonly IBlogService _blogService;
public BlogController(IBlogService blogService)
{
_blogService = blogService;
}
[HttpPut("security/content")]
public async Task<string> GetSecurityBlog([FromBody]string originContent)
{
return await _blogService.GetSecurityBlogAsync(originContent);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XUnitDemo.IService;
namespace XUnitDemo.Service
{
public class BlogService : IBlogService
{
public async Task<string> GetSecurityBlogAsync(string originContent)
{
if (string.IsNullOrWhiteSpace(originContent)) return originContent;
var sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
StringBuilder sbOriginContent = new StringBuilder(originContent);
foreach (var item in sensitiveList)
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", item);
if (File.Exists(filePath))
{
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using StreamReader sr = new StreamReader(fs);
var words = sr.ReadToEnd();
var wordList = words.Split("\r\n");
if (wordList.Any())
{
foreach (var word in wordList)
{
sbOriginContent.Replace(word, string.Join("", word.Select(s => "*")));
}
}
}
}
return await Task.FromResult(sbOriginContent.ToString());
}
}
}
0000
1111
2222
3333
4444
5555
using System.IO;
using System.Threading.Tasks;
namespace XUnitDemo.Infrastucture.Interface
{
public interface IFileManager
{
public Task<bool> IsExistsFileAsync(string filePath);
public Task<string> GetStringFromTxt(string filePath);
}
}
using System.IO;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
namespace XUnitDemo.Infrastucture
{
public class FileManager : IFileManager
{
public async Task<bool> IsExistsFileAsync(string filePath)
{
return await Task.FromResult(File.Exists(filePath));
}
public async Task<string> GetStringFromTxt(string filePath)
{
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using StreamReader sr = new StreamReader(fs);
return await sr.ReadToEndAsync();
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
namespace XUnitDemo.Service
{
public class BlogService : IBlogService
{
private readonly IFileManager _fileManager;
public BlogService(IFileManager fileManager)
{
_fileManager = fileManager;
}
public async Task<string> GetSecurityBlogAsync(string originContent)
{
if (string.IsNullOrWhiteSpace(originContent)) return originContent;
var sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
StringBuilder sbOriginContent = new StringBuilder(originContent);
foreach (var item in sensitiveList)
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", item);
if (await _fileManager.IsExistsFileAsync(filePath))
{
var words = await _fileManager.GetStringFromTxtAsync(filePath);
var wordList = words.Split("\r\n");
if (wordList.Any())
{
foreach (var word in wordList)
{
sbOriginContent.Replace(word, string.Join("", word.Select(s => "*")));
}
}
}
}
return await Task.FromResult(sbOriginContent.ToString());
}
}
}
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
using XUnitDemo.Service;
namespace XUnitDemo.NUnitTests.Blog
{
[TestFixture]
[Category("BlogService相关测试")]
public class BlogServiceUnitTest
{
private IBlogService _blogService;
[SetUp]
public void SetUp()
{
_blogService = new BlogService(new StubFileManager());
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ReturnSecurityContentAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
string targetContent = "**** **** **** **** **** **** 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(result, targetContent, $"{nameof(_blogService.GetSecurityBlogAsync)} 未能正确的将内容替换为合法内容");
}
[Test]
public async Task GetSecurityBlogAsync_OriginContentIsEmpty_ReturnEmptyAsync()
{
string originContent = "";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(result, string.Empty, $"{nameof(_blogService.GetSecurityBlogAsync)} 方法参数为空时,返回值也需要为空");
}
[TearDown]
public void TearDown()
{
_blogService = null;
}
}
internal class StubFileManager : IFileManager
{
public async Task<string> GetStringFromTxtAsync(string filePath)
{
var sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
if (Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", "Political.txt").Equals(filePath))
{
return await Task.FromResult("0000\r\n1111\r\n2222");
}
if (Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", "YellowRelated.txt").Equals(filePath))
{
return await Task.FromResult("3333\r\n4444\r\n5555");
}
return null;
}
public async Task<bool> IsExistsFileAsync(string filePath)
{
return await Task.FromResult(true);
}
}
}
重构:在不影响已有功能而改变代码设计的一种行为。
3.4.
3.4.
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("XUnitDemo.NUnitTests")]
namespace XUnitDemo.Service
{
public class BlogService : IBlogService
{
private readonly IFileManager _fileManager;
private ILogger _logger;
private static readonly List<string> _sensitiveList;
private int _effectiveSensitiveNum;
static BlogService()
{
_sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
}
public BlogService(IFileManager fileManager)
{
_fileManager = fileManager;
}
//#if DEBUG
// public BlogService(ILogger logger)
// {
// _logger = logger;
// }
//#endif
//[Conditional("DEBUG")]
//public void SetLogger(ILogger logger)
//{
// _logger = logger;
//}
internal BlogService(ILogger logger)
{
_logger = logger;
}
public async Task<string> GetSecurityBlogAsync(string originContent)
{
if (string.IsNullOrWhiteSpace(originContent)) return originContent;
_effectiveSensitiveNum = 0;
StringBuilder sbOriginContent = new StringBuilder(originContent);
foreach (var item in _sensitiveList)
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", item);
if (await _fileManager.IsExistsFileAsync(filePath))
{
var words = await _fileManager.GetStringFromTxtAsync(filePath);
var wordList = words.Split("\r\n");
if (wordList.Any())
{
_effectiveSensitiveNum++;
foreach (var word in wordList)
{
sbOriginContent.Replace(word, string.Join("", word.Select(s => "*")));
}
}
}
}
return await Task.FromResult(sbOriginContent.ToString());
}
public async Task<bool> IsAllSensitiveListIsEffectiveAsync()
{
if (_effectiveSensitiveNum == 0) return await Task.FromResult(false);
return await Task.FromResult(_sensitiveList.Count == _effectiveSensitiveNum);
}
}
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ReturnAllSensitiveListIsEffectiveAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(true,await _blogService.IsAllSensitiveListIsEffectiveAsync(), $"{nameof(_blogService.GetSecurityBlogAsync)} 敏感词文本列表与系统提供的数量不符");
}
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("XUnitDemo.NUnitTests")]
namespace XUnitDemo.Service
{
public class BlogService : IBlogService
{
private readonly IFileManager _fileManager;
private readonly ILoggerService _loggerService;
private ILogger _logger;
private static readonly List<string> _sensitiveList;
private int _effectiveSensitiveNum;
static BlogService()
{
_sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
}
public BlogService(IFileManager fileManager, ILoggerService loggerService)
{
_fileManager = fileManager;
_loggerService = loggerService;
}
public async Task<string> GetSecurityBlogAsync(string originContent)
{
if (string.IsNullOrWhiteSpace(originContent)) return originContent;
_effectiveSensitiveNum = 0;
StringBuilder sbOriginContent = new StringBuilder(originContent);
foreach (var item in _sensitiveList)
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", item);
if (await _fileManager.IsExistsFileAsync(filePath))
{
var words = await _fileManager.GetStringFromTxtAsync(filePath);
var wordList = words.Split("\r\n");
if (wordList.Any())
{
_effectiveSensitiveNum++;
foreach (var word in wordList)
{
sbOriginContent.Replace(word, string.Join("", word.Select(s => "*")));
}
}
}
}
if (originContent != sbOriginContent.ToString())
{
_loggerService.LogError($"{nameof(BlogService)}-{nameof(GetSecurityBlogAsync)}-【{originContent}】含有敏感字符", null);
}
return await Task.FromResult(sbOriginContent.ToString());
}
public async Task<bool> IsAllSensitiveListIsEffectiveAsync()
{
if (_effectiveSensitiveNum == 0) return await Task.FromResult(false);
return await Task.FromResult(_sensitiveList.Count == _effectiveSensitiveNum);
}
}
}
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
using XUnitDemo.Service;
namespace XUnitDemo.NUnitTests.Blog
{
[TestFixture]
[Category("BlogService相关测试")]
public class BlogServiceUnitTest
{
private IBlogService _blogService;
private MockLoggerService _mockLoggerService;
[SetUp]
public void SetUp()
{
_mockLoggerService = new MockLoggerService();
_blogService = new BlogService(new StubFileManager(), _mockLoggerService);
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ReturnSecurityContentAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
string targetContent = "**** **** **** **** **** **** 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(result, targetContent, $"{nameof(_blogService.GetSecurityBlogAsync)} 未能正确的将内容替换为合法内容");
}
[Test]
public async Task GetSecurityBlogAsync_OriginContentIsEmpty_ReturnEmptyAsync()
{
string originContent = "";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(result, string.Empty, $"{nameof(_blogService.GetSecurityBlogAsync)} 方法参数为空时,返回值也需要为空");
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ReturnAllSensitiveListIsEffectiveAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(true, await _blogService.IsAllSensitiveListIsEffectiveAsync(), $"{nameof(_blogService.GetSecurityBlogAsync)} 敏感词文本列表与系统提供的数量不符");
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ErrorMessageIsSendedAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(_mockLoggerService.LastErrorMessage, $"【{originContent}】含有敏感字符","LoggerService未能正确记录错误消息");
}
[TearDown]
public void TearDown()
{
_blogService = null;
}
}
internal class StubFileManager : IFileManager
{
public async Task<string> GetStringFromTxtAsync(string filePath)
{
var sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
if (Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", "Political.txt").Equals(filePath))
{
return await Task.FromResult("0000\r\n1111\r\n2222");
}
if (Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", "YellowRelated.txt").Equals(filePath))
{
return await Task.FromResult("3333\r\n4444\r\n5555");
}
return null;
}
public async Task<bool> IsExistsFileAsync(string filePath)
{
return await Task.FromResult(true);
}
}
internal class MockLoggerService : ILoggerService
{
public string LastErrorMessage { get; set; }
public void LogError(string content, Exception ex)
{
LastErrorMessage = content;
}
}
}
using System.Threading.Tasks;
namespace XUnitDemo.Infrastucture.Interface
{
public interface IEmailService
{
Task SendEmailAsync(string to, string from, string subject, string body);
}
}
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
namespace XUnitDemo.Infrastucture
{
public class EmailService : IEmailService
{
public async Task SendEmailAsync(string to, string from, string subject, string body)
{
//发送邮件逻辑
await Task.CompletedTask;
}
}
}
private IBlogService _blogService;
private MockLoggerService _mockLoggerService;
private StubLoggerService _stubLoggerService;
private MockEmailService _mockEmailService;
[SetUp]
public void SetUp()
{
//_mockLoggerService = new MockLoggerService();
//_blogService = new BlogService(new StubFileManager(), _mockLoggerService);
_stubLoggerService = new StubLoggerService();
_mockEmailService = new MockEmailService();
_blogService = new BlogService(new StubFileManager(), _stubLoggerService, _mockEmailService);
}
[Test]
public async Task GetSecurityBlogAsync_LoggerServiceThrow_SendEmail()
{
string error = "Custom Exception";
_stubLoggerService.Exception = new Exception(error);
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
await _blogService.GetSecurityBlogAsync(originContent);
Assert.Multiple(() =>
{
Assert.AreEqual("Harley", _mockEmailService.To);
Assert.AreEqual("System", _mockEmailService.From);
Assert.AreEqual("LoggerService抛出异常", _mockEmailService.Subject);
Assert.AreEqual(error, _mockEmailService.Body);
});
}
internal class StubLoggerService : ILoggerService
{
public Exception Exception { get; set; }
public void LogError(string content, Exception ex)
{
if (Exception is not null)
{
throw Exception;
}
}
}
internal class MockEmailService : IEmailService
{
public string To { get; set; }
public string From { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public async Task SendEmailAsync(string to, string from, string subject, string body)
{
To = to;
From = from;
Subject = subject;
Body = body;
await Task.CompletedTask;
}
}
3.4.
3.4.
3.4.
3.4.
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("XUnitDemo.NUnitTests")]
namespace XUnitDemo.Service
{
public class BlogService : IBlogService
{
private readonly IFileManager _fileManager;
private ILogger _logger;
public BlogService(IFileManager fileManager)
{
_fileManager = fileManager;
}
//#if DEBUG
// public BlogService(ILogger logger)
// {
// _logger = logger;
// }
//#endif
//[Conditional("DEBUG")]
//public void SetLogger(ILogger logger)
//{
// _logger = logger;
//}
internal BlogService(ILogger logger)
{
_logger = logger;
}
public async Task<string> GetSecurityBlogAsync(string originContent)
{
if (string.IsNullOrWhiteSpace(originContent)) return originContent;
var sensitiveList = new List<string> { "Political.txt", "YellowRelated.txt" };
StringBuilder sbOriginContent = new StringBuilder(originContent);
foreach (var item in sensitiveList)
{
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", item);
if (await _fileManager.IsExistsFileAsync(filePath))
{
var words = await _fileManager.GetStringFromTxtAsync(filePath);
var wordList = words.Split("\r\n");
if (wordList.Any())
{
foreach (var word in wordList)
{
sbOriginContent.Replace(word, string.Join("", word.Select(s => "*")));
}
}
}
}
return await Task.FromResult(sbOriginContent.ToString());
}
}
}
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using XUnitDemo.Infrastucture.Interface;
using XUnitDemo.IService;
using XUnitDemo.Service;
namespace XUnitDemo.NUnitTests.Blog
{
[Category("*使用隔离框架的测试*")]
[TestFixture]
public class MoqBlogServiceUnitTest
{
private List<string> SendEmailArgsList = new List<string>();
private IFileManager _fileManager;
private ILoggerService _loggerService;
private IEmailService _emailService;
private IBlogService _blogService;
[SetUp]
public void SetUp()
{
//FileManager stub object
//Return the fake result
var fileManager = new Mock<IFileManager>();
fileManager.Setup(f => f.GetStringFromTxtAsync(It.Is<string>(s => s == Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", "Political.txt")))).Returns(Task.FromResult("0000\r\n1111\r\n2222"));
fileManager.Setup(f => f.GetStringFromTxtAsync(It.Is<string>(s => s == Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SensitiveWords", "YellowRelated.txt")))).Returns(Task.FromResult("3333\r\n4444\r\n5555"));
fileManager.Setup(f => f.IsExistsFileAsync(It.IsAny<string>())).Returns(Task.FromResult(true));
_fileManager = fileManager.Object;
//LoggerService stub object
//Throw an exception
var loggerService = new Mock<ILoggerService>();
loggerService.Setup(s => s.LogError(It.IsAny<string>(), It.IsAny<Exception>())).Throws(new Exception("Custom Exception"));
_loggerService = loggerService.Object;
//EmailService mock object
var emailService = new Mock<IEmailService>();
emailService
.Setup(f => f.SendEmailAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => SendEmailArgsList.Clear())
.Returns(Task.CompletedTask)
.Callback<string, string, string, string>((arg1, arg2, arg3, arg4) =>
{
SendEmailArgsList.Add(arg1);
SendEmailArgsList.Add(arg2);
SendEmailArgsList.Add(arg3);
SendEmailArgsList.Add(arg4);
});
_emailService = emailService.Object;
_blogService = new BlogService(_fileManager, _loggerService, _emailService);
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ReturnSecurityContentAsync()
{
//Arrange
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
string targetContent = "**** **** **** **** **** **** 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
//Act
var blogService = new BlogService(_fileManager, _loggerService, _emailService);
var result = await _blogService.GetSecurityBlogAsync(originContent);
//Assert
Assert.Multiple(() =>
{
Assert.AreEqual(result, targetContent, "GetSecurityBlogAsync 未能正确的将内容替换为合法内容");
CollectionAssert.AreEqual(SendEmailArgsList, new List<string> { "Harley", "System", "LoggerService抛出异常", "Custom Exception" }, "GetSecurityBlogAsync记录错误日志时出现错误,未能正确发送邮件给开发者");
});
await Task.CompletedTask;
}
[Test]
public async Task GetSecurityBlogAsync_OriginContentIsEmpty_ReturnEmptyAsync()
{
string originContent = "";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(result, string.Empty, $"{nameof(_blogService.GetSecurityBlogAsync)} 方法参数为空时,返回值也需要为空");
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ReturnAllSensitiveListIsEffectiveAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
var result = await _blogService.GetSecurityBlogAsync(originContent);
Assert.AreEqual(true, await _blogService.IsAllSensitiveListIsEffectiveAsync(), $"{nameof(_blogService.GetSecurityBlogAsync)} 敏感词文本列表与系统提供的数量不符");
}
[Test]
public async Task GetSecurityBlogAsync_OriginContent_ErrorMessageIsSendedAsync()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
var loggerService = new Mock<ILoggerService>();
loggerService.Setup(s => s.LogError(It.IsAny<string>(), It.IsAny<Exception>())).Verifiable();
var blogService = new BlogService(_fileManager, loggerService.Object, _emailService);
await blogService.GetSecurityBlogAsync(originContent);
loggerService.Verify(s => s.LogError(It.IsAny<string>(), It.IsAny<Exception>()), "LoggerService未能正确记录错误消息");
}
[Test]
public async Task GetSecurityBlogAsync_LoggerServiceThrow_SendEmail()
{
string originContent = "1111 2222 3333 4444 0000 5555 为了节能环保000,为了环境安全,请使用可降解垃圾袋。";
await _blogService.GetSecurityBlogAsync(originContent);
CollectionAssert.AreEqual(SendEmailArgsList, new List<string> { "Harley", "System", "LoggerService抛出异常", "Custom Exception" }, "GetSecurityBlogAsync记录错误日志时出现错误,未能正确发送邮件给开发者");
}
[TearDown]
public void TearDown()
{ }
}
}
4.2.1、泛型类Mock的构造函数
public Mock();
public Mock(params object[] args);
public Mock(MockBehavior behavior);
public Mock(MockBehavior behavior, params object[] args);
public Mock(Expression<Func<T>> newExpression, MockBehavior behavior = MockBehavior.Loose);
public class Book
{
public string BookName;
public double Price;
public Book(string bookName)
{
BookName = bookName;
}
public Book(string bookName, double price)
{
BookName = bookName;
Price = price;
}
}
[Test]
public void Constructor_WithParams_CheckProperty()
{
var mockBook = new Mock<Book>("演进式架构");
var mockBook1 = new Mock<Book>("演进式架构", 59);
Assert.Multiple(()=> {
Assert.AreEqual("演进式架构", mockBook.Object.BookName);
Assert.AreEqual(0, mockBook.Object.Price);
Assert.AreEqual("演进式架构", mockBook1.Object.BookName);
Assert.AreEqual(59, mockBook1.Object.Price);
});
}
public interface IBookService
{
public bool AddBook(string bookName, double price);
}
public class BookService : IBookService
{
public bool AddBook(string bookName, double price)
{
return true;
}
}
/// <summary>
/// Moq.MockException : IBookService.AddBook("演进式架构", 59) invocation failed with mock behavior Strict.
/// All invocations on the mock must have a corresponding setup.
/// </summary>
[Category("*1、泛型类Mock的构造函数*")]
[Test]
public void Constructor_WithInterfaceMockBehaviorStrict_ThrowException()
{
var mockBookService = new Mock<IBookService>(MockBehavior.Strict);
mockBookService.Object.AddBook("演进式架构", 59);
}
/// <summary>
/// 无异常抛出
/// </summary>
[Category("*1、泛型类Mock的构造函数*")]
[Test]
public void Constructor_WithInterfaceMockBehaviorStrictAndSetup_NotThrowException()
{
var mockBookService = new Mock<IBookService>(MockBehavior.Strict);
mockBookService.Setup(s => s.AddBook("演进式架构", 59)).Returns(true);
mockBookService.Object.AddBook("演进式架构", 59);
}
/// <summary>
/// 无异常抛出
/// </summary>
[Category("*1、泛型类Mock的构造函数*")]
[Test]
public void Constructor_WithClassMockBehaviorStrict_ThrowException()
{
var mockBookService = new Mock<BookService>(MockBehavior.Strict);
mockBookService.Object.AddBook("演进式架构", 59);
}
/// <summary>
/// 无异常抛出
/// </summary>
[Category("*1、泛型类Mock的构造函数*")]
[Test]
public void Constructor_WithClassMockBehaviorLoose_NotThrowException()
{
var mockBookService = new Mock<BookService>(MockBehavior.Loose);
mockBookService.Object.AddBook("演进式架构", 59);
}
[Category("*1、泛型类Mock的构造函数*")]
[Test]
public void Constructor_WithNewExpression_ReturnMockBookServiceObject()
{
var mockBookService = new Mock<IBookService>(() => new BookService());
mockBookService.Object.AddBook("演进式架构", 59);
var mockBook = new Mock<Book>(() => new Book("演进式架构", 59));
Assert.Multiple(()=> {
Assert.AreEqual("演进式架构", mockBook.Object.BookName);
Assert.AreEqual(59, mockBook.Object.Price);
});
}
4.2.2、Mock的Setup方法
场景一:设置期望返回的值。
public interface IProductService
{
public bool AddProduct(string name);
}
public abstract class AbstractProductService : IProductService
{
public abstract bool AddProduct(string name);
}
public class ProductService : AbstractProductService
{
public override bool AddProduct(string name)
{
return DateTime.Now.Hour > 10;
}
}
[Category("*2、Mock的Setup用法*")]
[Test]
public void AddProduct_WithProductName_ReturnTrue()
{
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(s => s.AddProduct("演进式架构")).Returns(true);
var mockProductService1 = new Mock<AbstractProductService>();
mockProductService1.Setup(s => s.AddProduct("演进式架构")).Returns(true);
var mockProductService2 = new Mock<ProductService>();
mockProductService2.Setup(s => s.AddProduct("演进式架构")).Returns(true);
Assert.Multiple(()=> {
Assert.IsTrue(mockProductService.Object.AddProduct("演进式架构"));
Assert.IsFalse(mockProductService.Object.AddProduct("演进式架构_59"));
Assert.IsTrue(mockProductService1.Object.AddProduct("演进式架构"));
Assert.IsFalse(mockProductService1.Object.AddProduct("演进式架构_59"));
Assert.IsTrue(mockProductService2.Object.AddProduct("演进式架构"));
Assert.IsFalse(mockProductService2.Object.AddProduct("演进式架构_59"));
});
}
场景二:设置抛出异常。
public interface IProductService
{
public bool AddProduct(string name);
public bool DeleteProduct(Guid id);
}
[Category("*2、Mock的Setup用法*")]
[Test]
public void DeleteProduct_WithGuidEmpty_ThrowArgumentException()
{
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(s => s.DeleteProduct(Guid.Empty)).Throws(new ArgumentException("id"));
Assert.Multiple(()=> {
Assert.Throws<ArgumentException>(() => mockProductService.Object.DeleteProduct(Guid.Empty));
Assert.DoesNotThrow(() => mockProductService.Object.DeleteProduct(Guid.NewGuid()));
});
}
场景三:设置返回值,并使用参数计算返回值。
public interface IProductService
{
public bool AddProduct(string name);
public bool DeleteProduct(Guid id);
public string GetProudctName(Guid id);
}
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProudctName_WithGuid_ReturnParamAsResult()
{
var mockProductService = new Mock<IProductService>();
var id=Guid.Empty;
mockProductService.Setup(s => s.GetProudctName(It.IsAny<Guid>()))
.Returns<Guid>(s=>s.ToString()+"123");
var a = mockProductService.Object.GetProudctName(Guid.Empty);
Assert.AreEqual(a, $"{Guid.Empty.ToString()}123");
}
场景四:设置方法的回调,并在回调用使用方法参数,在调用方法前/后进行回调,相当于AOP(面向切面)。
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProudctName_WithGuid_ReturnEmptyWithAOP()
{
var mockProductService = new Mock<IProductService>();
var id = Guid.Empty;
mockProductService.Setup(s => s.GetProudctName(It.IsAny<Guid>()))
.Callback<Guid>(s => System.Diagnostics.Debug.WriteLine($"Before Invoke GetProudctName"))
.Returns<Guid>(s => string.Empty)
.Callback<Guid>(s => System.Diagnostics.Debug.WriteLine($"After Invoke GetProudctName"));
mockProductService.Object.GetProudctName(Guid.Empty);
Assert.Pass();
}
场景五:Setup与It静态类一起使用,在配置方法时进行方法参数的约束。
It.Is<TValue>():参数满足匿名函数的逻辑
It.IsInRange<TValue>():参数必须在范围区间
It.IsRegex():参数必须匹配正则表达式
It.IsAny<TValue>():参数可以是任意值
It.IsIn<TValue>():参数必须在集合中
It.IsNotIn<TValue>():参数不在集合中
It.IsNotNull<TValue>():参数不为NULL
mockProductService.Setup(s => s.GetProductList(It.Is<string>(a => a == "Book"), It.IsInRange<double>(20, 60, Moq.Range.Inclusive)))
.Returns(new List<ProductModel> { item });
当使用mockProductService.Object调用GetProductList时,只有在bookType满足It.Is<string>(a=>a=="Book")里面的匿名函数时,price范围在[20,60]之间,才会返回正确的结果,代码清单如下:
public interface IProductService
{
public bool AddProduct(string name);
public bool DeleteProduct(Guid id);
public string GetProudctName(Guid id);
public IEnumerable<ProductModel> GetProductList(string productType, double price);
}
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProductList_WithProductTypePriceInRange_ReturnProductList()
{
var mockProductService = new Mock<IProductService>();
var item = new ProductModel()
{
ProductId = Guid.NewGuid(),
ProductName = "演进式架构",
ProductPrice = 59
};
mockProductService.Setup(s => s.GetProductList(It.Is<string>(a => a == "Book"), It.IsInRange<double>(20, 60, Moq.Range.Inclusive)))
.Returns(new List<ProductModel> { item });
var productService = mockProductService.Object;
var result = productService.GetProductList("Book", 59);
var result1 = productService.GetProductList("Books", 59);
var result2 = productService.GetProductList("Book", 5);
Assert.Multiple(() =>
{
CollectionAssert.AreEqual(new List<ProductModel>() { item }, result);
CollectionAssert.AreEqual(new List<ProductModel>() { item }, result1, "param:bookType=Books,price=59返回的result1与预期的不相符");
CollectionAssert.AreEqual(new List<ProductModel>() { item }, result2, "param:bookType=Book,price=5返回的result2与预期的不相符");
});
}
mockProductService.Setup(s => s.GetProductList(It.IsRegex("^[1-9a-z_]{1,10}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase), It.IsAny<double>()))
.Returns(new List<ProductModel> { item });
当使用mockProductService.Object调用GetProductList时,只有在bookType满足^[1-9a-z_]{1,10}$正则表达式时,price可以是任意值,才会返回正确的结果,代码清单如下:
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProductList_WithProductTypeRegexPriceIsAny_ReturnProductList()
{
var mockProductService = new Mock<IProductService>();
var item = new ProductModel()
{
ProductId = Guid.NewGuid(),
ProductName = "演进式架构",
ProductPrice = 59
};
mockProductService.Setup(s => s.GetProductList(It.IsRegex("^[1-9a-z_]{1,10}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase), It.IsAny<double>()))
.Returns(new List<ProductModel> { item });
var productService = mockProductService.Object;
var result = productService.GetProductList("Book_123", 59);
var result1 = productService.GetProductList("BookBookBookBookBook", 123);
var result2 = productService.GetProductList("书籍", 5);
Assert.Multiple(() =>
{
CollectionAssert.AreEqual(new List<ProductModel>() { item }, result);
CollectionAssert.AreEqual(new List<ProductModel>() { item }, result1, "param:bookType=BookBookBookBookBook,price=123返回的result1与预期的不相符");
CollectionAssert.AreEqual(new List<ProductModel>() { item }, result2, "param:bookType=书籍,price=5返回的result2与预期的不相符");
});
}
场景五:设置当调用方法时,将会触发事件。
mockProductService.Object.Repaire(id);
public interface IProductService
{
public event EventHandler MyHandlerEvent;
public void Repaire(Guid id);
}
public abstract class AbstractProductService : IProductService
{
public event EventHandler MyHandlerEvent;
public void Repaire(Guid id)
{}
}
public class MyEventArgs : EventArgs {
public Guid Id { get; set; }
public MyEventArgs(Guid id)
{
Id = id;
}
}
public class ProductServiceHandler
{
private IProductService _productService;
public ProductServiceHandler(IProductService productService)
{
_productService = productService;
_productService.MyHandlerEvent += Invoke;
}
public void Invoke(object sender, EventArgs args)
{
System.Diagnostics.Debug.WriteLine($"This is {nameof(ProductServiceHandler)} {nameof(Invoke)} Id={((MyEventArgs)args).Id}");
}
}
[Category("*2、Mock的Setup用法*")]
[Test]
public void Repaire_WithIdIsAny_TriggerMyHandlerEvent()
{
var mockProductService = new Mock<IProductService>();
var id = Guid.NewGuid();
mockProductService.Setup(s => s.Repaire(It.IsAny<Guid>())).Raises<Guid>((s) =>
{
s.MyHandlerEvent += null;//这个注册的委托不会被调用,实际上是调用ProductServiceHandler中的Invoke方法
}, s => new MyEventArgs(id));
var myHandler = new ProductServiceHandler(mockProductService.Object);
System.Diagnostics.Debug.WriteLine($"This is {nameof(Repaire_WithIdIsAny_TriggerMyHandlerEvent)} Id={id}");
mockProductService.Object.Repaire(id);
}
场景六:与Verifiable,Verify一起使用,验证方法是否被调用/被调用了几次
其中Verifiable是标记当前设置需要执行Verify进行验证,在进行Verify时可以使用Mock实例的Verify,也可以使用Mock.Verify(只能验证是否被调用,无法验证执行具体次数)进行验证方法是否被调用,Mock.VerifyAll是指Setup之后无论是否标记Verifiable,都会进行验证。
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProductList_WithProductTypePrice_VerifyTwice()
{
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(s => s.GetProductList(It.IsAny<string>(), It.IsAny<double>()))
.Returns(new List<ProductModel>())
.Verifiable();
var result = mockProductService.Object.GetProductList("Book", 59);
mockProductService.Verify(s => s.GetProductList(It.IsAny<string>(), It.IsAny<double>()), Times.AtLeast(2), "GetProductList 没有按照预期执行2次");
}
var result = mockProductService.Object.GetProductList("Book", 59);
result = mockProductService.Object.GetProductList("Books", 5);
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProductList_WithProductTypePrice_MockVerify()
{
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(s => s.GetProductList(It.IsAny<string>(), It.IsAny<double>()))
.Returns(new List<ProductModel>())
.Verifiable("GetProductList没有按照预期执行一次");
//var result = mockProductService.Object.GetProductList("Book", 59);
Mock.Verify(mockProductService);
}
var result = mockProductService.Object.GetProductList("Book", 59);
[Category("*2、Mock的Setup用法*")]
[Test]
public void GetProductList_WithProductTypePrice_MockVerifyAll()
{
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(s => s.GetProductList(It.IsAny<string>(), It.IsAny<double>()))
.Returns(new List<ProductModel>());
var mockAbstractProductService = new Mock<AbstractProductService>();
mockAbstractProductService.Setup(s => s.GetProductList(It.IsAny<string>(), It.IsAny<double>()))
.Returns(new List<ProductModel>());
mockProductService.Object.GetProductList("Book", 59);
//mockAbstractProductService.Object.GetProductList("Book", 59);
Mock.VerifyAll(mockProductService, mockAbstractProductService);
}
mockAbstractProductService.Object.GetProductList("Book", 59);
4.2.3、Mock中的CallBase用法
[Category("*3、Mock的CallBase用法*")]
[Test]
public void GetProductName_WithIProductServiceAnyGuidCallBaseIsTrue_ReturnEmpty()
{
var mockProductService = new Mock<IProductService>() { CallBase = true };
//mockProductService.Setup(s => s.GetProudctName(It.IsAny<Guid>())).Returns(string.Empty);
var result = mockProductService.Object.GetProudctName(Guid.NewGuid());
Assert.AreEqual(string.Empty, result);
}
public abstract class AbstractProductService : IProductService
{
public virtual string GetProudctName(Guid id)
{
return "演进式架构";
}
}
[Category("*3、Mock的CallBase用法*")]
[Test]
public void GetProductName_WithAbstractProductServiceAnyGuidCallBaseIsTrue_ReturnBaseResult()
{
var mockProductService = new Mock<AbstractProductService>() { CallBase = true };
//mockProductService.Setup(s => s.GetProudctName(It.IsAny<Guid>())).Returns(string.Empty);
var result = mockProductService.Object.GetProudctName(Guid.NewGuid());
Assert.AreEqual("演进式架构", result);
}
public class ProductService : AbstractProductService
{
public string ToCurrentString()
{
return $"{ nameof(ProductService)}_{nameof(ToString)}";
}
}
[Category("*3、Mock的CallBase用法*")]
[Test]
public void ToCurrentString_WithProductServiceCallBaseIsTrue_ReturnBaseResult()
{
var mockProductService = new Mock<ProductService>() { CallBase = true };
mockProductService.Setup(s => s.ToCurrentString()).Returns(string.Empty);
var result = mockProductService.Object.ToCurrentString();
Assert.AreEqual("ProductService_ToString", result);
}
4.2.4、Mock的DefaultValue属性
public interface IRolePermissionService
{
public IRoleRepository RoleRepository { get; set; }
public IPermissionRepository PermissionRepository { get; set; }
public IEnumerable<string> GetPermissionList(Guid roleId);
}
public interface IRoleRepository
{
public IDbContext DbContext { get; set; }
public string GetRoleName(Guid roleId);
}
public interface IPermissionRepository
{
public IDbContext DbContext { get; set; }
public string GetPermissionName(Guid permissionId);
}
public interface IDbContext {
}
[Category("*4、Mock的DefaultValue用法*")]
[Test]
public void MockDefaultValue_WithDefaultValueMock_ReturnExpect()
{
var mockRolePermissionService = new Mock<IRolePermissionService>() { DefaultValue = DefaultValue.Mock };
var roleRepos = mockRolePermissionService.Object.RoleRepository;
var permissionRepos = mockRolePermissionService.Object.PermissionRepository;
Assert.Multiple(()=> {
Assert.NotNull(roleRepos);
Assert.NotNull(permissionRepos);
Assert.NotNull(roleRepos.DbContext);
Assert.NotNull(permissionRepos.DbContext);
});
}
[Category("*4、Mock的DefaultValue用法*")]
[Test]
public void GetRoleName_WidthRolePermissionServiceDefaultValueMock_ReturnExpect()
{
var mockRolePermissionService = new Mock<IRolePermissionService>() { DefaultValue = DefaultValue.Mock };
var roleRepos = mockRolePermissionService.Object.RoleRepository;
var mockRoleRepos = Mock.Get(roleRepos);
mockRoleRepos.Setup(s => s.GetRoleName(It.IsAny<Guid>())).Returns("Admin");
var result = mockRolePermissionService.Object.RoleRepository.GetRoleName(Guid.NewGuid());
Assert.AreEqual("Admin", result);
}
4.2.5、Mock的SetupProperty与SetupAllProperties
public interface ICar
{
public IEnumerable<IWheel> Wheels { get; set; }
public string CarBrand { get; set; }
public string CarModel { get; set; }
}
public interface IWheel
{
public string WheelHub { get; set; }
public string WheelTyre { get; set; }
public string WheelTyreTube { get; set; }
}
public class Car : ICar
{
public IEnumerable<IWheel> Wheels { get; set; }
public string CarBrand { get; set; }
public string CarModel { get; set; }
}
public class CarWheel : IWheel
{
public string WheelHub { get; set; }
public string WheelTyre { get; set; }
public string WheelTyreTube { get; set; }
}
[Category("*5、SetupProperty与SetupAllProperties的用法*")]
[Test]
public void CheckProperty_WithSetupProperty_ShouldPass()
{
var mockCar = new Mock<ICar>();
//mockCar.SetupProperty(s => s.CarBrand).SetupProperty(s => s.CarModel);
//mockCar.SetupProperty(s => s.CarBrand, "一汽大众")
// .SetupProperty(s => s.CarModel, "七座SUV");
mockCar.Object.CarBrand = "一汽大众";
mockCar.Object.CarModel = "七座SUV";
Assert.Multiple(() =>
{
Assert.AreEqual("七座SUV", mockCar.Object.CarModel);
Assert.AreEqual("一汽大众", mockCar.Object.CarBrand);
});
}
mockCar.SetupProperty(s => s.CarBrand).SetupProperty(s => s.CarModel);
mockCar.SetupProperty(s => s.CarBrand, "一汽大众")
.SetupProperty(s => s.CarModel, "七座SUV");
mockCar.Object.CarBrand = "上汽大众";
mockCar.Object.CarModel = "五座SUV";
[Category("*5、SetupProperty与SetupAllProperties的用法*")]
[Test]
public void CheckProperty_WithSetupAllProperties_ShouldPass()
{
var mockCar = new Mock<ICar>();
mockCar.SetupAllProperties();
mockCar.Object.CarBrand = "上汽大众";
mockCar.Object.CarModel = "五座SUV";
Assert.Multiple(() =>
{
Assert.AreEqual("七座SUV", mockCar.Object.CarModel);
Assert.AreEqual("一汽大众", mockCar.Object.CarBrand);
});
}
[Category("*5、SetupProperty与SetupAllProperties的用法*")]
[Test]
public void CheckProperty_WithSetupSetVerifySet_ShouldPass()
{
var mockCar = new Mock<ICar>();
mockCar.SetupSet(s => s.CarBrand ="上汽大众");
mockCar.SetupSet(s => s.CarModel = "五座SUV");
//mockCar.Object.CarBrand = "上汽大众";
//mockCar.Object.CarModel = "五座SUV";
mockCar.Object.CarBrand = "一汽大众";
mockCar.Object.CarModel = "七座SUV";
mockCar.VerifySet(s => s.CarBrand = "上汽大众"); ;
mockCar.VerifySet(s => s.CarModel = "五座SUV"); ;
}
mockCar.Object.CarBrand = "上汽大众";
mockCar.Object.CarModel = "五座SUV";
//mockCar.Object.CarBrand = "一汽大众";
//mockCar.Object.CarModel = "七座SUV"
public interface IComputer
{
public string ComputerType { get; set; }
}
public interface IScreen
{
public string GetScreenType();
}
public interface IMainBoard
{
public ICpu GetCpu();
}
public interface IKeyboard
{
public string GetKeyboardType();
}
public interface ICpu
{
public string GetCpuType();
}
[Category("*6、Mock的As用法*")]
[Test]
public void MockAs_WithMultipleInterface_ShouldPass()
{
var mockComputer = new Mock<IComputer>();
var mockKeyBoard = mockComputer.As<IKeyboard>();
var mockScreen = mockComputer.As<IScreen>();
var mockCpu = mockComputer.As<ICpu>();
mockKeyBoard.Setup(s => s.GetKeyboardType()).Returns("机械键盘");
mockScreen.Setup(s => s.GetScreenType()).Returns("OLED");
mockCpu.Setup(s => s.GetCpuType()).Returns("Intel-11代I7");
var keyboardType = ((dynamic)mockComputer.Object).GetKeyboardType();
var screenType = ((dynamic)mockComputer.Object).GetScreenType();
var cpuType = ((dynamic)mockComputer.Object).GetCpuType();
Assert.Multiple(() =>
{
Assert.AreEqual("机械键盘", keyboardType);
Assert.AreEqual("OLED", screenType);
Assert.AreEqual("Intel-11代I7", cpuType);
});
}
[Category("*6、Mock的As用法*")]
[Test]
public void MockAs_WithMultipleInterfaceAndInvokePropertyBeforeAs_ShouldPass()
{
var mockComputer = new Mock<IComputer>();
mockComputer.Setup(s => s.ComputerType).Returns("台式机");
var computerType = mockComputer.Object.ComputerType;
var mockKeyBoard = mockComputer.As<IKeyboard>();
var mockScreen = mockComputer.As<IScreen>();
var mockCpu = mockComputer.As<ICpu>();
mockKeyBoard.Setup(s => s.GetKeyboardType()).Returns("机械键盘");
mockScreen.Setup(s => s.GetScreenType()).Returns("OLED");
mockCpu.Setup(s => s.GetCpuType()).Returns("Intel-11代I7");
var keyboardType = ((dynamic)mockComputer.Object).GetKeyboardType();
var screenType = ((dynamic)mockComputer.Object).GetScreenType();
var cpuType = ((dynamic)mockComputer.Object).GetCpuType();
Assert.Multiple(() =>
{
Assert.AreEqual("台式机", computerType);
Assert.AreEqual("机械键盘", keyboardType);
Assert.AreEqual("OLED", screenType);
Assert.AreEqual("Intel-11代I7", cpuType);
});
}

4.2.7、Mock如何设置异步方法
mockProductService.Setup(s => s.AddProductAsync(It.IsAny<string>()).Result).Returns(true);
mockProductService.Setup(s => s.AddProductAsync(It.IsAny<string>())).ReturnsAsync(true);
public interface IProductService
{
public Task<bool> AddProductAsync(string name);
}
[Category("7、Mock的异步方法设置")]
[Test]
public async Task AddProductAsync_WithName_ReturnTrue()
{
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(s => s.AddProductAsync(It.IsAny<string>()).Result).Returns(true);
//mockProductService.Setup(s => s.AddProductAsync(It.IsAny<string>())).ReturnsAsync(true);
var result = await mockProductService.Object.AddProductAsync("演进式架构");
Assert.AreEqual(true, result);
}
4.2.8、Mock的Sequence用法
[Category("*8、Mock的Sequence用法*")]
[Test]
public void GetProductName_WithId_ReturnDifferenctValueInMultipleInvoke()
{
var mockProductService = new Mock<IProductService>();
mockProductService.SetupSequence(s => s.GetProudctName(It.IsAny<Guid>()))
.Returns("渐进式架构")
.Returns("Vue3实战")
.Returns("Docker实战")
.Returns("微服务架构设计模式");
var result = mockProductService.Object.GetProudctName(Guid.Empty);
var result1 = mockProductService.Object.GetProudctName(Guid.Empty);
var result2 = mockProductService.Object.GetProudctName(Guid.Empty);
var result3 = mockProductService.Object.GetProudctName(Guid.Empty);
Assert.Multiple(() =>
{
Assert.AreEqual("渐进式架构", result);
Assert.AreEqual("Vue3实战", result1);
Assert.AreEqual("Docker实战", result2);
Assert.AreEqual("微服务架构设计模式", result3);
});
}
[Category("*8、Mock的Sequence用法*")]
[Test]
public void InSequence_WithMultipleNonSequenceInvoke_ThrowException()
{
var mockProductService = new Mock<IProductService>(MockBehavior.Strict);
var sequence = new MockSequence();
mockProductService.InSequence(sequence)
.Setup(s => s.AddProductAsync(It.IsAny<string>())).ReturnsAsync(true);
mockProductService.InSequence(sequence)
.Setup(s => s.GetProudctName(It.IsAny<Guid>())).Returns("渐进式架构");
mockProductService.InSequence(sequence)
.Setup(s => s.DeleteProduct(It.IsAny<Guid>())).Returns(true);
mockProductService.Object.AddProductAsync("渐进式架构");
mockProductService.Object.GetProudctName(Guid.Empty);
mockProductService.Object.DeleteProduct(Guid.Empty);
}
[Category("*8、Mock的Sequence用法*")]
[Test]
public void InSequence_WithMultipleInSequenceInvoke_WillPass()
{
var mockProductService = new Mock<IProductService>(MockBehavior.Strict);
var sequence = new MockSequence();
mockProductService.InSequence(sequence)
.Setup(s => s.AddProductAsync(It.IsAny<string>())).ReturnsAsync(true);
mockProductService.InSequence(sequence)
.Setup(s => s.GetProudctName(It.IsAny<Guid>())).Returns("渐进式架构");
mockProductService.InSequence(sequence)
.Setup(s => s.DeleteProduct(It.IsAny<Guid>())).Returns(true);
mockProductService.Object.AddProductAsync("渐进式架构");
mockProductService.Object.GetProudctName(Guid.Empty);
mockProductService.Object.DeleteProduct(Guid.Empty);
}
4.2.9、Mock的Protected用法
//需要引用命名空间
using Moq.Protected
public class Calculator
{
public Calculator(int first, int second, double number, double divisor)
{
First = first;
Second = second;
Number = number;
Divisor = divisor;
}
protected int First { get; set; }
protected int Second { get; set; }
protected double Number { get; set; }
protected double Divisor { get; set; }
public double Sum()
{
return (First + Second) * GetPercent() * GetSalt(0.9);
}
public double Division()
{
return Number * GetPercent() * GetSalt(0.9) / Divisor;
}
protected virtual double GetPercent()
{
return 0.9;
}
protected virtual double GetSalt(double salt)
{
return salt;
}
}
[Category("*9、Mock的Protected用法*")]
[Test]
public void Calculate_WithProtectedMembers_CanAccessProtectedMembers()
{
var mockCalculator = new Mock<Calculator>(12, 10, 100, 5);
mockCalculator.Protected().Setup<double>("GetPercent").Returns(0.5);
mockCalculator.Protected().Setup<double>("GetSalt",ItExpr.IsAny<double>()).Returns(0.9);
var obj = mockCalculator.Object;
var sum = obj.Sum();
var division = obj.Division();
Assert.Multiple(()=> {
Assert.AreEqual(22 * 0.5 *0.9, sum);
Assert.AreEqual(100 * 0.5 * 0.9 / 5, division);
});
}
//单独的接口,未被Caculator类继承
public interface ICaculatorProtectedMembers
{
double GetPercent();
double GetSalt(double salt);
}
[Category("*9、Mock的Protected用法*")]
[Test]
public void Calculate_WithProtectedMembersUnRelatedInterface_CanAccessProtectedMembers()
{
var mockCalculator = new Mock<Calculator>(12, 10, 100, 5);
var caculatorProtectedMembers= mockCalculator.Protected().As<ICaculatorProtectedMembers>();
caculatorProtectedMembers.Setup(s => s.GetPercent()).Returns(0.6);
caculatorProtectedMembers.Setup(s => s.GetSalt(It.IsAny<double>())).Returns(0.8);
var obj = mockCalculator.Object;
var sum = obj.Sum();
var division = obj.Division();
Assert.Multiple(() =>
{
Assert.AreEqual(22 * 0.6 * 0.8, sum);
Assert.AreEqual(100 * 0.6 * 0.8 / 5, division);
});
}
4.2.10、Mock的Of用法
[Category("*10、Mock的Of用法*")]
[Test]
public void MockOf_WithLinq_QuickSetup()
{
var context = Mock.Of<HttpContext>(hc =>
hc.User.Identity.IsAuthenticated == true &&
hc.User.Identity.Name == "harley" &&
hc.Response.ContentType == "application/json" &&
hc.RequestServices == Mock.Of<IServiceProvider>
(a => a.GetService(typeof(ICaculatorProtectedMembers)) == Mock.Of<ICaculatorProtectedMembers>
(p => p.GetPercent() == 0.2 && p.GetSalt(It.IsAny<double>()) == 0.3)));
Assert.Multiple(() =>
{
Assert.AreEqual(true, context.User.Identity.IsAuthenticated);
Assert.AreEqual("harley", context.User.Identity.Name);
Assert.AreEqual("application/json", context.Response.ContentType);
Assert.AreEqual(0.2,context.RequestServices.GetService<ICaculatorProtectedMembers>().GetPercent());
Assert.AreEqual(0.3, context.RequestServices.GetService<ICaculatorProtectedMembers>().GetSalt(1));
;
});
}
public interface IRedisService
{
public bool SaveJsonToString<T>(T TObject);
public bool SavePersonToString<T>(T TObject);
}
public class Person
{
public string Name { get; set; }
}
public class Male : Person
{
}
[Category("*10、Mock的Of用法*")]
[Test]
public void SaveJsonToString_TObject_ReturnTrue()
{
var mockRedisService = new Mock<IRedisService>();
mockRedisService.Setup(s => s.SaveJsonToString(It.IsAny<It.IsAnyType>()))
.Returns(true);
var kv = new KeyValuePair<string, string>("Harley", "Coder");
Assert.Multiple(() =>
{
Assert.AreEqual(true, mockRedisService.Object.SaveJsonToString(kv));
Assert.AreEqual(true, mockRedisService.Object.SaveJsonToString(new { Name = "Harley", JobType = "Coder" }));
});
}
[Category("*10、Mock的Of用法*")]
[Test]
public void SavePersonToString_WithMale_ReturnTrue()
{
var mockRedisService = new Mock<IRedisService>();
var male = new Male { Name = "Harley" };
mockRedisService.Setup(s => s.SavePersonToString(It.IsAny<It.IsSubtype<Person>>())).Returns(true);
var result1 = mockRedisService.Object.SavePersonToString(new { Name = "Harley", JobType = "Coder" });
var result2 = mockRedisService.Object.SavePersonToString(new Person { Name = "Harley" });
var result3 = mockRedisService.Object.SavePersonToString(new Male { Name = "Harley" });
Assert.Multiple(() =>
{
Assert.AreEqual(true, result1,"使用匿名类型生成的对象无法通过测试");
Assert.AreEqual(true, result2,"使用Person类型生成的对象无法通过测试");
Assert.AreEqual(true, result3, "使用Male类型生成的对象无法通过测试");
});
}
public class ProductServiceHandler
{
private IProductService _productService;
public ProductServiceHandler(IProductService productService)
{
_productService = productService;
_productService.MyHandlerEvent += Invoke;
}
public void Invoke(object sender, EventArgs args)
{
System.Diagnostics.Debug.WriteLine($"This is {nameof(ProductServiceHandler)} {nameof(Invoke)} Id={((MyEventArgs)args).Id}");
}
}
[Category("*12、Mock的Events用法*")]
[Test]
public void Repaire_WithIdIsAny_RaiseMyHandlerEvent()
{
var mockProductService = new Mock<IProductService>();
var id = Guid.NewGuid();
var myHandler = new ProductServiceHandler(mockProductService.Object);
System.Diagnostics.Debug.WriteLine($"This is {nameof(Repaire_WithIdIsAny_RaiseMyHandlerEvent)} Id={id}");
mockProductService.Setup(s => s.Repaire(It.IsAny<Guid>())).Verifiable();
//这个注册的委托不会被调用,实际上是触发ProductServiceHandler中的Invoke委托
mockProductService.Raise(s => s.MyHandlerEvent += null, new MyEventArgs(id));
mockProductService.Object.Repaire(id);
}
public class ProductServiceHandler
{
private IProductService _productService;
public ProductServiceHandler(IProductService productService)
{
_productService = productService;
_productService.MyHandlerEvent += Invoke;
_productService.MyHandlerEvent -= Invoke;
}
public void Invoke(object sender, EventArgs args)
{
System.Diagnostics.Debug.WriteLine($"This is {nameof(ProductServiceHandler)} {nameof(Invoke)} Id={((MyEventArgs)args).Id}");
}
}
[Category("*12、Mock的Events用法*")]
[Test]
public void MockSetupAddRemove_WithIdIsAny_RaiseMyHandlerEvent()
{
var mockProductService = new Mock<IProductService>();
var id = Guid.NewGuid();
mockProductService.SetupAdd(s => s.MyHandlerEvent += It.IsAny<EventHandler>());
var myHandler = new ProductServiceHandler(mockProductService.Object);
mockProductService.VerifyAdd(s => s.MyHandlerEvent += It.IsAny<EventHandler>(), Times.Once);
mockProductService.VerifyRemove(s => s.MyHandlerEvent -= It.IsAny<EventHandler>(), Times.Never);
}

文章中用到的示例代码可以在GitHub上进行下载,地址为:
References:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!