如何在单元测试简单方法时引发异常抛出

时间:2017-07-27 18:43:50

标签: c# unit-testing dependency-injection moq automoq

我有一个服务调用包装器,它只会将参数转发给调用中的服务。包装器的原因是我们可以使用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);
        }
    }
}

我必须编写以下单元测试

  1. 当发生内部异常时,GetLocalWeather()不会使应用程序崩溃
  2. GetLocalWeather()记录例外
  3. 现在要测试两种情况,我需要以某种方式引发崩溃;我如何使用NUnit和Automoq / Moq做到这一点?

2 个答案:

答案 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
    }
}