编辑
2025-02-04
C# 应用
00
请注意,本文编写于 91 天前,最后修改于 91 天前,其中某些信息可能已经过时。

目录

原型开发和快速验证
依赖注入容器的扩展
模拟复杂的外部系统
行为驱动开发(BDD)中的应用
模拟事件和回调
结论

Moq作为C#中最流行的模拟框架之一,其强大的功能不仅限于单元测试。本文将探讨Moq在实际应用开发中的一些创新用法,展示如何利用其灵活性来解决各种编程挑战。

原型开发和快速验证

在项目初期,我们经常需要快速验证想法或构建原型。Moq可以帮助我们模拟尚未实现的组件或服务,使得我们能够专注于核心功能的开发。

C#
public interface IWeatherService { Task<int> GetTemperature(string city); }
C#
// 定义 WeatherApp 类 public class WeatherApp { private readonly IWeatherService _weatherService; public WeatherApp(IWeatherService weatherService) { _weatherService = weatherService; } public async Task<string> GetWeatherReport(string city) { try { int temperature = await _weatherService.GetTemperature(city); return $"The current temperature in {city} is {temperature}°C."; } catch (Exception ex) { return $"Error getting weather for {city}: {ex.Message}"; } } }
C#
static async Task Main(string[] args) { var mockWeatherService = new Mock<IWeatherService>(); mockWeatherService.Setup(s => s.GetTemperature(It.IsAny<string>())) .ReturnsAsync((string city) => city.GetHashCode() % 40); // 模拟温度 var app = new WeatherApp(mockWeatherService.Object); // 使用模拟的服务测试 WeatherApp string report = await app.GetWeatherReport("New York"); Console.WriteLine(report); report = await app.GetWeatherReport("London"); Console.WriteLine(report); }

image.png

依赖注入容器的扩展

Moq可以与依赖注入容器结合使用,为开发和调试提供更大的灵活性。

C#
// 扩展方法定义 public static class ServiceCollectionExtensions { public static IServiceCollection AddMockedService<TService>( this IServiceCollection services, Action<Mock<TService>> setup) where TService : class { var mock = new Mock<TService>(); setup(mock); services.AddSingleton(mock.Object); return services; } }
C#
// 示例服务接口 public interface IUserRepository { Task<User> GetByIdAsync(int id); Task<bool> SaveAsync(User user); } // 用户模型 public class User { public int Id { get; set; } public string Name { get; set; } } // 使用模拟服务的应用程序代码 public class UserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } public async Task<string> GetUserNameById(int id) { var user = await _userRepository.GetByIdAsync(id); return user?.Name ?? "User not found"; } public async Task<bool> CreateUser(string name) { var user = new User { Name = name }; return await _userRepository.SaveAsync(user); } } internal class Program { static async Task Main(string[] args) { // 配置依赖注入容器 var services = new ServiceCollection(); // 添加模拟的 IUserRepository services.AddMockedService<IUserRepository>(mock => { mock.Setup(r => r.GetByIdAsync(It.IsAny<int>())) .ReturnsAsync((int id) => new User { Id = id, Name = $"User {id}" }); mock.Setup(r => r.SaveAsync(It.IsAny<User>())) .ReturnsAsync(true); }); // 添加 UserService services.AddTransient<UserService>(); // 构建服务提供者 var serviceProvider = services.BuildServiceProvider(); // 使用 UserService var userService = serviceProvider.GetRequiredService<UserService>(); // 测试获取用户名 var userName = await userService.GetUserNameById(1); Console.WriteLine($"User name: {userName}"); // 测试创建用户 var created = await userService.CreateUser("New User"); Console.WriteLine($"User created: {created}"); } }

模拟复杂的外部系统

在与复杂的外部系统集成时,Moq可以帮助我们模拟各种场景,包括错误处理和边界情况。

C#
public class PaymentResult { // 指示支付是否成功 public bool Success { get; set; } // 支付网关返回的交易ID public string TransactionId { get; set; } // 如果支付失败,这里会包含错误信息 public string ErrorMessage { get; set; } // 支付金额 public decimal Amount { get; set; } // 支付时间 public DateTime TransactionDate { get; set; } // 支付方式(例如:信用卡、PayPal等) public string PaymentMethod { get; set; } // 支付状态(例如:已处理、待处理、已退款等) public PaymentStatus Status { get; set; } // 卡号后四位(如果适用) public string LastFourDigits { get; set; } // 构造函数 public PaymentResult() { TransactionDate = DateTime.UtcNow; } // 用于表示支付状态的枚举 public enum PaymentStatus { Processed, Pending, Refunded, Failed } } public interface IPaymentGateway { Task<PaymentResult> ProcessPayment(decimal amount, string cardNumber); } public class PaymentProcessor { private readonly IPaymentGateway _gateway; public PaymentProcessor(IPaymentGateway gateway) { _gateway = gateway; } public async Task<string> ProcessPayment(decimal amount, string cardNumber) { try { var result = await _gateway.ProcessPayment(amount, cardNumber); if (result.Success) { return $"Payment successful. Transaction ID: {result.TransactionId}"; } else { return $"Payment failed: {result.ErrorMessage}"; } } catch (HttpRequestException ex) { return $"Network error occurred: {ex.Message}"; } } } internal class Program { static async Task Main(string[] args) { var mockGateway = new Mock<IPaymentGateway>(); // 模拟成功支付 mockGateway.Setup(g => g.ProcessPayment(It.Is<decimal>(a => a <= 1000), It.IsAny<string>())) .ReturnsAsync(new PaymentResult { Success = true, TransactionId = Guid.NewGuid().ToString() }); // 模拟金额过大导致的失败 mockGateway.Setup(g => g.ProcessPayment(It.Is<decimal>(a => a > 1000), It.IsAny<string>())) .ReturnsAsync(new PaymentResult { Success = false, ErrorMessage = "Amount exceeds limit" }); // 模拟网络错误 mockGateway.Setup(g => g.ProcessPayment(It.Is<decimal>(a => a == 999), It.IsAny<string>())) .ThrowsAsync(new HttpRequestException("Network error")); var processor = new PaymentProcessor(mockGateway.Object); Console.WriteLine(await processor.ProcessPayment(500, "1234-5678-9012-3456")); // 应该成功 Console.WriteLine(await processor.ProcessPayment(1500, "1234-5678-9012-3456")); // 应该失败 Console.WriteLine(await processor.ProcessPayment(999, "1234-5678-9012-3456")); // 应该抛出网络错误 } }

image.png

行为驱动开发(BDD)中的应用

Moq可以在行为驱动开发中发挥重要作用,帮助我们定义和验证系统的预期行为。

C#
public class When_user_logs_in { private Mock<IAuthenticationService> _authService; private LoginManager _loginManager; private LoginResult _result; public When_user_logs_in() { _authService = new Mock<IAuthenticationService>(); _loginManager = new LoginManager(_authService.Object); } public void Given_valid_credentials() { _authService.Setup(s => s.Authenticate("user", "password")) .ReturnsAsync(true); } public async Task When_login_is_attempted() { _result = await _loginManager.Login("user", "password"); } public void Then_login_should_succeed() { VerifyLoginSuccess(_result); } private void VerifyLoginSuccess(LoginResult result) { if (!result.Success) { throw new Exception($"Expected login to succeed, but it failed. Message: {result.Message}"); } if (result.Message != "Login successful.") { throw new Exception($"Expected success message, but got: {result.Message}"); } Console.WriteLine("Login succeeded as expected."); } } // 假设的 LoginResult 和 LoginManager 类定义 public class LoginResult { public bool Success { get; set; } public string Message { get; set; } } public class LoginManager { private readonly IAuthenticationService _authService; public LoginManager(IAuthenticationService authService) { _authService = authService; } public async Task<LoginResult> Login(string username, string password) { bool isAuthenticated = await _authService.Authenticate(username, password); return new LoginResult { Success = isAuthenticated, Message = isAuthenticated ? "Login successful." : "Login failed." }; } } public interface IAuthenticationService { Task<bool> Authenticate(string username, string password); } internal class Program { static async Task Main(string[] args) { Console.WriteLine("Running BDD-style test for user login..."); var test = new When_user_logs_in(); test.Given_valid_credentials(); await test.When_login_is_attempted(); test.Then_login_should_succeed(); Console.WriteLine("Test completed."); } }

模拟事件和回调

Moq不仅可以模拟方法和属性,还可以模拟事件和复杂的回调逻辑。

C#
public class StockPriceChangedEventArgs : EventArgs { public decimal OldPrice { get; } public decimal NewPrice { get; } public StockPriceChangedEventArgs(decimal oldPrice, decimal newPrice) { OldPrice = oldPrice; NewPrice = newPrice; } } public interface IStockTicker { event EventHandler<StockPriceChangedEventArgs> PriceChanged; decimal CurrentPrice { get; } } public class StockMonitor { private readonly IStockTicker _ticker; public StockMonitor(IStockTicker ticker) { _ticker = ticker; _ticker.PriceChanged += OnPriceChanged; } private void OnPriceChanged(object sender, StockPriceChangedEventArgs e) { Console.WriteLine($"Stock price changed: Old: {e.OldPrice:C}, New: {e.NewPrice:C}, Change: {((e.NewPrice - e.OldPrice) / e.OldPrice):P2}"); } public decimal GetCurrentPrice() => _ticker.CurrentPrice; } class Program { static async Task Main(string[] args) { var mockTicker = new Mock<IStockTicker>(); decimal currentPrice = 100m; mockTicker.Setup(t => t.CurrentPrice).Returns(() => currentPrice); var monitor = new StockMonitor(mockTicker.Object); Console.WriteLine("Simulating stock price changes for 10 seconds..."); var startTime = DateTime.Now; var random = new Random(); while ((DateTime.Now - startTime).TotalSeconds < 10) { decimal oldPrice = currentPrice; currentPrice *= (1 + ((decimal)random.NextDouble() - 0.5m) * 0.1m); mockTicker.Raise(t => t.PriceChanged += null, mockTicker.Object, new StockPriceChangedEventArgs(oldPrice, currentPrice)); Console.WriteLine($"Current price: {monitor.GetCurrentPrice():C}"); await Task.Delay(1000); // Wait for 1 second } Console.WriteLine("Simulation completed."); } }

image.png

结论

Moq的应用远不止于单元测试。通过创造性地使用Moq,我们可以简化开发过程,提高代码质量,并更有效地处理复杂的编程场景。无论是在原型开发、系统集成还是行为驱动开发中,Moq都能成为强大的工具,帮助开发者构建更加健壮和可维护的应用程序。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!