In MSTest, How can I verify exact error message using [ExpectedException(typeof(ApplicationException))]

前端 未结 11 1502
南方客
南方客 2020-12-15 16:01

Using MSTest how can I verify the exact error message coming from a test method? I know [ExpectedException(typeof(ApplicationException), error msg)] doesn\'t co

相关标签:
11条回答
  • 2020-12-15 16:49

    The annoyance with annotations and try/catch blocks is that you don't have a clean separation between the ACT and ASSERT phases of the test. A simpler appraoch is to "capture" the exception as part of the ACT phase using a utitlity routine such as:

    public static class Catch
    {
        public static Exception Exception(Action action)
        {
            Exception exception = null;
    
            try
            {
                action();
            }
            catch (Exception ex)
            {
                exception = ex;
            }
    
            return exception;
        }
    }
    

    This allows you to do:

    // ACT
    var actualException = Catch.Exception(() => DoSomething())
    
    // ASSERT
    Assert.IsNotNull(actualException, "No exception thrown");
    Assert.IsInstanceOfType(actualException, expectedType);
    Assert.AreEqual(expectedExceptionMessage, actualException.Message);
    
    0 讨论(0)
  • 2020-12-15 16:50

    Use this little helper class:

    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);               
            }
        }
    }
    

    Usage:

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

    The advantage of explicit catching exceptions is that teh test does not succeed when another member (e.g. during the initialization) throws the exception.

    0 讨论(0)
  • 2020-12-15 16:52

    I was looking for a way to check the presence and type of inner exception with mstest and I found this question. I known it a 2 years old topic but since my solution is not here, let me share it.

    To me, the most elegant way to solve the problem is to create a derived attribute, here's mine (sorry but comments and strings are in french, my natural language, but it should be obvious) :

    #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
        }
    }
    

    Now, use this attribute with named parameters instead of the "ExpectedException". With my attribute you can check if there is an inner exception, message of the exception and inner exception, use a regex to match messages, etc... You can adapt as you want.

    0 讨论(0)
  • 2020-12-15 16:53

    Fluent Assertions (NuGet) has a very "language natural" syntax for defining expectations in unit tests:

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

    There are multiple variations for checking the error message with any algorithm (Where(e => ...) as well as checking into inner exceptions and their messages.

    0 讨论(0)
  • 2020-12-15 16:53

    In MSTest there's no built-in way of doing it. This is about as 'elegant' as it gets:

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

    However, you could consider porting xUnit.NET's Assert.Throws API to a custom library - that's what we did.

    You could also go to Microsoft Connect an vote on this suggestion.

    0 讨论(0)
提交回复
热议问题