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);
}
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")); // 应该抛出网络错误
}
}
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.");
}
}
Moq的应用远不止于单元测试。通过创造性地使用Moq,我们可以简化开发过程,提高代码质量,并更有效地处理复杂的编程场景。无论是在原型开发、系统集成还是行为驱动开发中,Moq都能成为强大的工具,帮助开发者构建更加健壮和可维护的应用程序。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!