我如何使用Assert(或其他测试类)来验证在使用MSTest/Microsoft.VisualStudio.TestTools.UnitTesting时抛出了异常?


当前回答

我知道这个帖子很老了,有很多很好的答案,但值得一提的是,局部函数可以以一种非常简单的方式提供帮助。

//Arrange

//Act
void LocalFunction() => mr.ActualMethod(params);

//Assert
Assert.Throws<Exception>(LocalFunction);

其他回答

这是测试方法的一个属性…你不使用Assert。看起来是这样的:

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()

在我正在做的一个项目中,我们有另一个解决方案。

首先,我不喜欢ExpectedExceptionAttribute,因为它确实考虑了导致异常的方法调用。

我用一个helper方法来代替它。

Test

[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
     var file = File.Create("Accounts.bin");
     file.WriteByte(1);
     file.Close();

     IAccountRepository repo = new FileAccountRepository();
     TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
}

HelperMethod

public static TException AssertThrows<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (TException ex)
        {
            return ex;
        }
        Assert.Fail("Expected exception was not thrown");

        return null;
    }

很整洁,不是吗?)

如果你正在使用MSTest,它最初没有ExpectedException属性,你可以这样做:

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}

我不建议使用ExpectedException属性(因为它约束太大,容易出错),也不建议在每个测试中编写一个try/catch块(因为它太复杂,容易出错)。使用设计良好的断言方法——可以由您的测试框架提供,也可以自己编写。下面是我写的和用的。

public static class ExceptionAssert
{
    private static T GetException<T>(Action action, string message="") where T : Exception
    {
        try
        {
            action();
        }
        catch (T exception)
        {
            return exception;
        }
        throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
    }

    public static void Propagates<T>(Action action) where T : Exception
    {
        Propagates<T>(action, "");
    }

    public static void Propagates<T>(Action action, string message) where T : Exception
    {
        GetException<T>(action, message);
    }

    public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
    {
        Propagates(action, validation, "");
    }

    public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
    {
        validation(GetException<T>(action, message));
    }
}

使用示例:

    [TestMethod]
    public void Run_PropagatesWin32Exception_ForInvalidExeFile()
    {
        (test setup that might propagate Win32Exception)
        ExceptionAssert.Propagates<Win32Exception>(
            () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
        (more asserts or something)
    }

    [TestMethod]
    public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
    {
        (test setup that might propagate FileNotFoundException)
        ExceptionAssert.Propagates<FileNotFoundException>(
            () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
            e => StringAssert.Contains(e.Message, "NotThere.exe"));
        (more asserts or something)
    }

笔记

返回异常而不是支持验证回调是一个合理的想法,只是这样做会使这个断言的调用语法与我使用的其他断言非常不同。

与其他人不同,我使用“propagates”而不是“throws”,因为我们只能测试异常是否从调用中传播。我们不能直接测试是否抛出了异常。但我想你可以把投掷想象成:被扔出去而没有被接住。

最后认为

在切换到这种方法之前,我考虑过在测试只验证异常类型时使用ExpectedException属性,在需要更多验证时使用try/catch块。但是,我不仅要考虑在每个测试中使用哪种技术,而且随着需求的变化将代码从一种技术更改为另一种技术也不是一件简单的工作。使用一种一致的方法可以节省脑力。

总而言之,这种方法具有易用性、灵活性和健壮性(很难做错)。

更新

我的方法对于mstest V2来说不再有价值了,它似乎已经在2018年问世了。使用Assert.ThrowsException。

除非你一直在使用旧版本的mstest。那么,我的方法仍然适用。

作为一种替代方法,您可以尝试测试异常实际上是在您的测试中的下两行中抛出的。

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);