MOQ - 验证异常是否被抛出

56

我在我的测试中使用MOQ框架。 有一个场景,我期望会抛出故障异常。 如何验证它是否被抛出?

public void Koko(List<string?> list) 
{ 
   foreach(string? str in list) 
   { 
        if (str != null) someProperty.Foo(str); 
        else throw new FormatException(); 
   } 
} 
7个回答

75

5
如果你正在使用最新的FluentAssertions包,你可以像这样验证是否抛出了异常:Action action = () => foo.Bar(); action.Should().Throw<Exception>(); - Singularity222
1
如果 Bar() 是一个 async 方法呢? - Lukas
2
@Lukas FluentAssertions 对这种情况有很好的支持。 Func<Task> act = () => foo.BarAsync(); await act.Should().ThrowAsync<SomeException>(); - treze
在这种情况下,别忘了将 lambda 函数标记为异步 (... async () => ...)。 - ksyrium
不回答问题。AlanT的解决方案才是答案。 - Will

32

我可能误解了你的意图,但据我所见,在测试异常被抛出时,没有必要对模拟对象进行任何操作。

看起来你有一个类,其中有一个名为 Foo 的方法,它接受一个字符串参数 - 让我们将其称为 InnerClass。

public class InnerClass {
    public virtual void Foo(string str) {
         // do something with the string
    }
}

包含一个名为someProperty的内部类属性(InnerClass),其中包含一个以List<string>为参数的成员Koko。

public class OuterClass {

    private readonly InnerClass someProperty;

    public OuterClass(InnerClass someProperty) {
        this.someProperty = someProperty;
    }

    public void Koko(List<string> list) {
         foreach (var str in list) {
              if (str != null)
                   someProperty.Foo(str);
              else
                   throw new FormatException();
          }
    } 
}

注意:我无法编译List<string?> - 显示其基础类型(string)必须是非空的。据我所知,只有值类型需要可空性,引用类型默认可空。

看起来您想测试如果您传递包含任何null的字符串列表是否会抛出FormatException。

如果是这样,那么MOQ的唯一原因就是可以让我们不用担心InnerClass的功能。Foo是一个方法,所以除非我们使用严格的mocks,否则我们可以只创建一个没有其他设置的InnerClass mock。

有一个属性[ExpectedException],我们可以用它来标记我们的测试以验证异常是否已被抛出。

[TestMethod]
[ExpectedException(typeof(FormatException))]
public void ExceptionThrown() {

    var list = new List<string>() {
        "Abel",
        "Baker",
        null,
        "Charlie"
    };

    var outer = new OuterClass(new Mock<InnerClass>().Object);
    outer.Koko(list);

}

如果抛出了FormatException,则此测试将通过,否则将失败。


我之前不知道ExpectedException属性,这太棒了!谢谢。 - chrjs
4
这是真正的答案。谢谢。 - CubanTurin
使用ExpectedException属性的缺点是您无法明确指定何种操作应该引发异常。 - treze

17

阅读这些答案后,我意识到还有另一种使用NUnit解决此问题的方法。以下代码从异常中获取异常文本,并验证错误消息文本。

var ex = Assert.Throws<SomeException>(() => foo.Bar());
Assert.That(ex.Message, Is.EqualTo("Expected exception text");

我无法让最新版本的NUnit使用装饰符/属性语法(在上面AlanT的回答中),不知道为什么,但无论我尝试做什么,它都会出错。

但我无法让最新版本的NUnit使用装饰符/属性语法(在AlanT的上面的答案中),不管我尝试什么,它都会抱怨。


13
请阅读此Moq简介。以下是设置在调用DoSomething方法时抛出InvalidOperationException的方法:
mock.Setup(foo => foo.DoSomething()).Throws<InvalidOperationException>();

那么只需验证方法是否被调用。如果已调用,则会引发异常。

mock.Verify(foo => foo.DoSomething());

1
我不想设置一个异常。 我想验证它是否被抛出。 - Gal Ziv
我会更详细地解释这种情况。也许我的方法不正确。我有一个foreach循环在两个对象上运行。一个是可用的,另一个是null。我想验证两件事,第一是在可用对象上运行方法,第二是对于null对象抛出异常(在方法中检查null,如果是则手动抛出异常)。 - Gal Ziv
很抱歉,禁止宣传代码(工作政策)。 - Gal Ziv
@GZ 你不需要粘贴完全相同的代码。编写描述您问题的类似代码即可。从您的描述中,我们无法清楚地了解您正在模拟什么以及您要验证什么。实际上,您是在询问如何验证Moq是否抛出异常 - 上面的代码就可以做到这一点。 - Sergey Berezovskiy
1
@GZ 我还没有看到任何模拟数据。顺便问一下,你知道字符串可以为空吗? - Sergey Berezovskiy
显示剩余4条评论

12

你可以使用 NUnit Asserts 测试是否抛出了异常:

Assert.That(() => testObject.methodToTest(), Throws.TypeOf<FaultException>());

9

这是一个老问题,但实际上没有源代码显示解决方案,所以我做了以下操作:

var correctExceptionThrown = false;

try
{
    _myClass.DoSomething(x);
}
catch (Exception ex)
{
    if (ex.Message == "Expected message")
        correctExceptionThrown = true;
}                    

Assert.IsTrue(correctExceptionThrown);

注意,与其检查消息,您可以捕获特定类型的异常(通常更可取)。


2

好的,我是如下方式解决的。

由于异常破环了我的测试,所以我在try-catch中将该方法调用放入"Because"块中。

然后我可以使用简单的验证。

感谢所有的帮助者...


5
考虑将您的解决方案代码发布,这样其他人就可以学习或评论它。您也可以将此答案标记为解决方案。 - g t
3
听起来AlanT给出了正确的答案。在Moq中,如果您想测试Foo(string)会抛出ArgumentException,则应该使用ExpectedException - steve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接