在MSTest中,如何使用[ExpectedException(typeof(ApplicationException))]验证确切的错误消息

时间:2009-12-22 07:47:01

标签: unit-testing automation mstest

使用MSTest如何验证来自测试方法的确切错误消息?我知道[ExpectedException(typeof(ApplicationException), error msg)]没有比较来自我的测试方法的错误消息,不过它正在进行的其他单元测试框架中。

解决这个问题的一种方法是使用一些try catch块编写单元测试,但我需要再写4行。

是否有任何最明智的方法来检查错误消息。

干杯, Pritam

11 个答案:

答案 0 :(得分:35)

您可以create your own ExpectedException attributeAssertException发送namespace TestProject { public sealed class MyExpectedException : ExpectedExceptionBaseAttribute { private Type _expectedExceptionType; private string _expectedExceptionMessage; public MyExpectedException(Type expectedExceptionType) { _expectedExceptionType = expectedExceptionType; _expectedExceptionMessage = string.Empty; } public MyExpectedException(Type expectedExceptionType, string expectedExceptionMessage) { _expectedExceptionType = expectedExceptionType; _expectedExceptionMessage = expectedExceptionMessage; } protected override void Verify(Exception exception) { Assert.IsNotNull(exception); Assert.IsInstanceOfType(exception, _expectedExceptionType, "Wrong type of exception was thrown."); if(!_expectedExceptionMessage.Length.Equals(0)) { Assert.AreEqual(_expectedExceptionMessage, exception.Message, "Wrong exception message was returned."); } } } } 的消息。

<强>代码

[TestMethod]
[MyExpectedException(typeof(Exception), "Error")]
public void TestMethod()
{
    throw new Exception("Error");
}

<强>用法

{{1}}

答案 1 :(得分:15)

使用这个小助手类:

public static class ExceptionAssert
{
    public static void Throws<TException>(Action action, string message)
        where TException : Exception
    {
        try
        {
            action();

            Assert.Fail("Exception of type {0} expected; got none exception", typeof(TException).Name);
        }
        catch (TException ex)
        {
            Assert.AreEqual(message, ex.Message);
        }
        catch (Exception ex)
        {
            Assert.Fail("Exception of type {0} expected; got exception of type {1}", typeof(TException).Name, ex.GetType().Name);               
        }
    }
}

用法:

Foo foo = new Foo();
foo.Property = 42;

ExceptionAssert.Throws<InvalidOperationException>(() => foo.DoSomethingCritical(), "You cannot do anything when Property is 42.");

显式捕获异常的优点是,当另一个成员(例如在初始化期间)抛出异常时,测试不会成功。

答案 2 :(得分:5)

Fluent AssertionsNuGet)具有非常“语言自然”的语法,用于定义单元测试中的期望:

objectundertest.Invoking(o => o.MethodUnderTest()).ShouldThrow<ExpectedException>()
    .WithMessage("the expected error message");

使用任何算法(Where(e => ...)检查错误消息以及检查内部异常及其消息有多种变体。

答案 3 :(得分:5)

我正在寻找一种方法来检查mstest的内部异常的存在和类型,我发现了这个问题。我知道它是一个2岁的主题,但由于我的解决方案不在这里,让我分享一下。

对我来说,解决问题最优雅的方法是创建一个派生属性,这是我的(对不起,但评论和字符串是法语,我的自然语言,但应该是显而易见的):

#region Références
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Text.RegularExpressions;
#endregion

namespace MsTestEx
{
    /// <summary>
    /// Extention de l'attribut ExpectedException permettant de vérifier plus d'éléments (Message, InnerException, ...)
    /// </summary>
    public class ExpectedExceptionEx
        : ExpectedExceptionBaseAttribute
    {
        #region Variables locales
        private Type    _ExpectedException              = null;
        private string  _ExpectedMessage                = null;
        private Type    _ExpectedInnerException         = null;
        private string  _ExpectedInnerExceptionMessage  = null;
        private bool    _IsExpectedMessageRegex         = false;
        private bool    _IsExpectedInnerMessageRegex    = false;
        private bool    _AllowDerivedType               = false;
        private bool    _AllowInnerExceptionDerivedType = false;

        private bool    _CheckExpectedMessage           = false;
        private bool    _CheckInnerExceptionType        = false;
        private bool    _CheckInnerExceptionMessage     = false;
        #endregion

        #region Propriétés
        /// <summary>
        /// Vérifie que le message de l'exception correspond à celui-ci.
        /// </summary>
        public string ExpectedMessage
        {
            get { return _ExpectedMessage; }
            set { _ExpectedMessage = value; _CheckExpectedMessage = true; }
        }

        /// <summary>
        /// Vérifie que le message de l'inner-exception correspond à celui-ci.
        /// </summary>
        public string ExpectedInnerExceptionMessage
        {
            get { return _ExpectedInnerExceptionMessage; }
            set { _ExpectedInnerExceptionMessage = value; _CheckInnerExceptionMessage = true; }
        }

        /// <summary>
        /// Vérifie que l'exception possède bien une inner-exception du type spécifié.
        /// Spécifier "null" pour vérifier l'absence d'inner-exception.
        /// </summary>
        public Type ExpectedInnerException
        {
            get { return _ExpectedInnerException; }
            set { _ExpectedInnerException = value; _CheckInnerExceptionType = true; }
        }

        /// <summary>
        /// Indique si le message attendu est exprimé via une expression rationnelle.
        /// </summary>
        public bool IsExpectedMessageRegex
        {
            get { return _IsExpectedMessageRegex; }
            set { _IsExpectedMessageRegex = value; }
        }

        /// <summary>
        /// Indique si le message attendu de l'inner-exception est exprimé via une expression rationnelle.
        /// </summary>
        public bool IsExpectedInnerMessageRegex
        {
            get { return _IsExpectedInnerMessageRegex; }
            set { _IsExpectedInnerMessageRegex = value; }
        }

        /// <summary>
        /// Indique si les exceptions dérivées sont acceptées.
        /// </summary>
        public bool AllowDerivedType
        {
            get { return _AllowDerivedType; }
            set { _AllowDerivedType = value; }
        }

        /// <summary>
        /// Indique si les inner-exceptions dérivées sont acceptées.
        /// </summary>
        public bool AllowInnerExceptionDerivedType
        {
            get { return _AllowInnerExceptionDerivedType; }
            set { _AllowInnerExceptionDerivedType = value; }
        }
        #endregion

        #region Constructeurs
        /// <summary>
        /// Indique le type d'exception attendu par le test.
        /// </summary>
        /// <param name="expectedException">Type de l'exception attendu.</param>
        public ExpectedExceptionEx(Type expectedException)
        {
            _ExpectedException = expectedException;
        }
        #endregion

        #region Méthodes
        /// <summary>
        /// Effectue la vérification.
        /// </summary>
        /// <param name="exception">Exception levée.</param>
        protected override void Verify(Exception exception)
        {
            Assert.IsNotNull(exception); // Pas eu d'exception, ce n'est pas normal

            // Vérification du type de l'exception
            Type actualType = exception.GetType();
            if (_AllowDerivedType) Assert.IsTrue(_ExpectedException.IsAssignableFrom(actualType), "L'exception reçue n'est pas du type spécifié ni d'un type dérivé.");
            else Assert.AreEqual(_ExpectedException, actualType, "L'exception reçue n'est pas du type spécifié.");

            // Vérification du message de l'exception
            if (_CheckExpectedMessage)
            {
                if (_IsExpectedMessageRegex)
                    Assert.IsTrue(Regex.IsMatch(exception.Message, _ExpectedMessage), "Le message de l'exception ne correspond pas à l'expression rationnelle");
                else
                {
                    string s1, s2;
                    if (exception.Message.Length > _ExpectedMessage.Length)
                    {
                        s1 = exception.Message;
                        s2 = _ExpectedMessage;
                    }
                    else
                    {
                        s1 = _ExpectedMessage;
                        s2 = exception.Message;
                    }
                    Assert.IsTrue(s1.Contains(s2), "Le message de l'exception ne contient pas et n'est pas contenu par le message attendu.");
                }
            }

            if (_CheckInnerExceptionType)
            {
                if (_ExpectedInnerException == null) Assert.IsNotNull(exception.InnerException);
                else
                {
                    // Vérification du type de l'exception
                    actualType = exception.InnerException.GetType();
                    if (_AllowInnerExceptionDerivedType) Assert.IsTrue(_ExpectedInnerException.IsAssignableFrom(actualType), "L'inner-exception reçue n'est pas du type spécifié ni d'un type dérivé.");
                    else Assert.AreEqual(_ExpectedInnerException, actualType, "L'inner-exception reçue n'est pas du type spécifié.");
                }
            }

            if (_CheckInnerExceptionMessage)
            {
                Assert.IsNotNull(exception.InnerException);
                if (_IsExpectedInnerMessageRegex)
                    Assert.IsTrue(Regex.IsMatch(exception.InnerException.Message, _ExpectedInnerExceptionMessage), "Le message de l'exception ne correspond pas à l'expression rationnelle");
                else
                {
                    string s1, s2;
                    if (exception.InnerException.Message.Length > _ExpectedInnerExceptionMessage.Length)
                    {
                        s1 = exception.InnerException.Message;
                        s2 = _ExpectedInnerExceptionMessage;
                    }
                    else
                    {
                        s1 = _ExpectedInnerExceptionMessage;
                        s2 = exception.InnerException.Message;
                    }
                    Assert.IsTrue(s1.Contains(s2), "Le message de l'inner-exception ne contient pas et n'est pas contenu par le message attendu.");
                }
            }
        }
        #endregion
    }
}

现在,将此属性与命名参数一起使用,而不是“ExpectedException”。使用我的属性,您可以检查是否存在内部异常,异常消息和内部异常,使用正则表达式匹配消息等... 你可以随心所欲地适应。

答案 4 :(得分:4)

在MSTest中没有内置的方法。这就像它的“优雅”一样:

[TestMethod]
public void Test8()
{
    var t = new Thrower();
    try
    {
        t.DoStuffThatThrows();
        Assert.Fail("Exception expected.");
    }
    catch (InvalidOperationException e)
    {
        Assert.AreEqual("Boo hiss!", e.Message);
    }
}

但是,您可以考虑将xUnit.NET的Assert.Throws API移植到自定义库中 - 这就是我们所做的。

您还可以转到Microsoft Connect vote on this suggestion

答案 5 :(得分:2)

MSTest v2支持Assert.Throws和Assert.ThrowsAsync,它们返回捕获的异常。

此处是有关如何升级到MSTest v2的文章:https://blogs.msdn.microsoft.com/devops/2017/09/01/upgrade-to-mstest-v2/

以下是示例用法:

var myObject = new MyObject();
var ex = Assert.Throws<ArgumentNullException>(() => myObject.Do(null));
StringAssert.Contains(ex.Message, "Parameter name: myArg");

答案 6 :(得分:1)

注释和try / catch块的烦恼在于您在测试的ACT和ASSERT阶段之间没有清晰的分离。一个更简单的appraoch是&#34;捕获&#34;作为ACT阶段的一部分,例外情况是使用例如:

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;
    private readonly IHostingEnvironment _env;
    private readonly HttpContext _http;

    public ViewRenderService(IRazorViewEngine razorViewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider, IHostingEnvironment env, IHttpContextAccessor ctx)
    {
        _razorViewEngine = razorViewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; _env = env; _http = ctx.HttpContext;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        var actionContext = new ActionContext(_http, new RouteData(), new ActionDescriptor());

        using (var sw = new StringWriter())
        {
            var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
            //var viewResult = _razorViewEngine.GetView(_env.WebRootPath, viewName, false); // For views outside the usual Views folder
            if (viewResult.View == null)
            {
                throw new ArgumentNullException($"{viewName} does not match any available view");
            }
            var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
            {
                Model = model
            };
            var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary, new TempDataDictionary(_http, _tempDataProvider), sw, new HtmlHelperOptions());
            viewContext.RouteData = _http.GetRouteData();
            await viewResult.View.RenderAsync(viewContext);
            return sw.ToString();
        }
    }
}

这允许你这样做:

{{1}}

答案 7 :(得分:0)

使用MSTest,你不能这样做。

您已经知道此问题的解决方案:在catch块中声明异常消息。

答案 8 :(得分:0)

MbUnit也可以这样做:

[Test]
[Row(ExpectedExceptionMessage="my message")]
void TestBlah(...

答案 9 :(得分:0)

此代码在异步/等待情况下执行此操作:

Code Reference

public async static Task AssertThrowsAsync<T>(Task task, string expectedMessage) where T : Exception
{
    try
    {
        await task;
    }
    catch (Exception ex)
    {
        if (ex is T)
        {
            Assert.AreEqual(expectedMessage, ex.Message);
            return;
        }

        Assert.Fail($"Expection exception type: {typeof(T)} Actual type: {ex.GetType()}");
    }

    Assert.Fail($"No exception thrown");
}

示例用法: Code Reference

    [TestMethod]
    public async Task TestBadRequestThrowsHttpStatusCodeException()
    {
        var mockHttp = new MockHttpMessageHandler();

        const HttpStatusCode statusCode = HttpStatusCode.BadRequest;

        mockHttp.When("https://restcountries.eu/rest/v2/")
                .Respond(statusCode, "application/json", JsonConvert.SerializeObject(new { Message = "Test", ErrorCode = 100 }));

        var httpClient = mockHttp.ToHttpClient();

        var factory = new SingletonHttpClientFactory(httpClient);

        var baseUri = new Uri("https://restcountries.eu/rest/v2/");
        var client = new Client(new NewtonsoftSerializationAdapter(), httpClientFactory: factory, baseUri: baseUri, logger: _logger.Object);

        await AssertThrowsAsync<HttpStatusException>(client.GetAsync<List<RestCountry>>(), Messages.GetErrorMessageNonSuccess((int)statusCode, baseUri));
    }

这相当于同步情况

public static void AssertThrows<T>(Action action, string expectedMessage) where T : Exception
{
    try
    {
        action.Invoke();
    }
    catch (Exception ex)
    {
        if (ex is T)
        {
            Assert.AreEqual(expectedMessage, ex.Message);
            return;
        }

        Assert.Fail($"Expection exception type: {typeof(T)} Actual type: {ex.GetType()}");
    }

    Assert.Fail($"No exception thrown");
}

注意:这断言异常是从给定类型继承的。如果要检查特定类型,则应检查类型是否相等,而不要使用is运算符。

答案 10 :(得分:-1)

更新:糟糕..在MSTest中看到你想要这个。抱歉。速读&amp;被你的头衔误导了。

Callum Hibbert 尝试此扩展程序,看看它是否有效。

旧回应:

您可以使用NUnit 2.4及更高版本执行此操作。 请参阅此处的ExpectedException documentation

[ExpectedException( typeof( ArgumentException), ExpectedMessage="unspecified", MatchType=MessageMatch.Contains )]
public void TestMethod()
{
...

MatchType可以是Exact(默认),Contains或Regex ..它几乎可以处理80%的用例。如果验证过于复杂,还有一种高级异常处理程序方法方法。从未单独使用过它..但还不需要它。