我有一个服务调用包装器,它只会将参数转发给调用中的服务。包装器的原因是我们可以使用DI容器对Wrapper进行注入,从而模拟单元测试。
以下是包装器的外观
public class WeatherChannelWrapper : IWeatherServiceWrapper
{
public GetLocalWeather(string zipcode)
{
TWCProxy.GetCurrentCondition(zipcode, DateTime.Now.ToString("MM/dd/yyyy hh:mm"));
}
}
一切正常,现在我需要在TWCProxy的崩溃上吞下例外。所以现在我的包装器看起来像这样。
public class WeatherChannelWrapper : IWeatherServiceWrapper
{
private readonly IExceptionLogger exceptionLogger;
public WeatherChannelWrapper(IExceptionLogger logger)
{
this.exceptionLogger = logger;
}
public GetLocalWeather(string zipcode)
{
try
{
TWCProxy.GetCurrentCondition(zipcode, DateTime.Now.ToString("MM/dd/yyyy hh:mm"));
}
catch (Exception e)
{
exceptionLogger.Log(e);
}
}
}
我必须编写以下单元测试
现在要测试两种情况,我需要以某种方式引发崩溃;我如何使用NUnit和Automoq / Moq做到这一点?
答案 0 :(得分:1)
如果不重构代码,您需要做的是在TWCProxy
类上使用填充程序。 Moq不提供垫片,因此您可能需要使用第三方库。
我不确定你是否可以使用像Microsoft Fakes这样的东西,但如果你这样做,可以在单元测试中轻松实现。
https://msdn.microsoft.com/en-us/library/hh549175.aspx
首先,引用您的第三方库并创建一个假的lib。在VS(我使用企业,不确定哪些版本具有该功能)这可以在您的测试项目中完成,通过引用库,然后在参考中,右键单击库和Add Fake Assembly
。然后建立你的项目。
然后,在您的单元测试中,您可以按如下方式设置垫片
[TestMethod]
public void MyTestMethod()
{
using (ShimsContext.Create())
{
// fake assembly will have a long method name for the signature of the call. omitted for simplicity
ShimTWCProxy.GetCurrentCondition[...] = () => {throw new Exception("message")};
// instantiate and execute test code
var logger = new Mock<IExceptionLogger>();
var testedClass = new WeatherChannelWrapper (logger.Object);
testedClass.GetLocalWeather("...");
svc.Verify(x => x.Log(It.IsAny<string>()), Times.Once);
}
}
另一种方法是将一个方法委托传递给类,该委托可以默认为您的静态类,然后允许您在运行时覆盖它,尽管这可能有点混乱。如果你想要一个例子,请告诉我。
请注意,MS Fakes与一些测试跑者/框架不能很好地配合。例如,它似乎适用于NUnit和MSTest适配器,以及MSTest和Resharper测试运行器,但不适用于NUnit和Resharper运行器。
也可能还有其他的填充/伪装解决方案,但我并不是立即意识到它们。
答案 1 :(得分:1)
更改WeatherChannelWrapper,以便您可以覆盖它以引发异常。例如,通过添加下面的CallTwcProxy等受保护的虚拟方法:
public class WeatherChannelWrapper : IWeatherServiceWrapper
{
private readonly IExceptionLogger exceptionLogger;
public WeatherChannelWrapper(IExceptionLogger logger)
{
this.exceptionLogger = logger;
}
public GetLocalWeather(string zipcode)
{
try
{
CallTwcProxy(zipcode);
}
catch (Exception e)
{
exceptionLogger.Log(e);
}
}
protected virtual void CallTwcProxy(string zipcode)
{
TWCProxy.GetCurrentCondition(zipcode, DateTime.Now.ToString("MM/dd/yyyy hh:mm"));
}
}
然后你可以通过覆盖测试项目中的CallTwcProxy方法来伪造它以引发异常:
public class FakeWeatherChannelWrapper : WeatherChannelWrapper
{
protected override virtual void CallTwcProxy(string zipcode)
{
throw new Exception("force exception to throw");
}
}
现在你有一个假的WeatherChannelWrapper会抛出一个你可以在测试中使用的异常:
public class WeatherChannelWrapperTests
{
[Test]
public void GetLocalWeather_WhenInternalExceptionOccurs_LogsException_Test()
{
var loggerMock = new Mock<IExceptionLogger>();
var sut = new FakeWeatherChannelWrapper(loggerMock.Object);
sut.GetLocalWeather("12345");
// Assert Exception is logged
loggerMock.Verify(logger => logger.Log(It.IsAny<Exception>()), Times.Once);
// And practically this also tests that it doesn't crash even if exception is thrown
}
}