Unit test that list created is as expected and that exceptions are thrown as expected.

AlHal2
AlHal2 used Ask the Experts™
on
I'm writing a .NET Core application in Visual Studio 2017.
This code generates a list from a comma separated set of string values.  How can I unit test it?  I think my difficulty is that the list is of type card rather than a simple string.

 
//2H,3C should generate a list
//2H3C should generate an exception as there is no comma
//2H,2H should generate an exception as the cards are duplicated
//2H,JR,JR,JR should generate an exception as there are more than two jokers

CardApp.Controllers.CardsController ScoreTest1 = new CardApp.Controllers.CardsController();
            List<CardApp.Models.Card> Deck = new List<CardApp.Models.Card>();
CollectionAssert.AreEqual(new List <CardApp.Models.Card>(){ }, ScoreTest1.SetUpList( "2H,3C"));

Open in new window



        public List<Card> SetUpList(string s)
        {
            


                for (int i = 2; i < s.Length; i += 3)
                {
                    if (s.Substring(i, 1) != ",")
                    {
                        throw new Exception ( "Deck must consist of two digit card followed by comma");
                        
                    }
                }
            List<Card> cards = new List<Card>();
            foreach (string item in s.ToUpper().Split(',').ToList<string>())
            {
                Card temp = new Card(item);
                bool cardExists = cards.Any(x => x.Value == temp.Value && x.Type == temp.Type);
                if (!cardExists)
                {
                    cards.Add(temp);
                }
                else if (item.Equals("JR", StringComparison.OrdinalIgnoreCase))
                {
                    cards.Add(temp);
                }
                else
                {
                    throw new Exception( string.Format("Duplicate Card Detected : {1} of {0}", temp.Type, temp.Value));
                }
            }
            return cards;

        }
public class Card : ICard
    {
        //public Card()
        //{ }
        public Card(string initValue)
        {
            if (initValue.Equals("JR", StringComparison.OrdinalIgnoreCase))
            {
                IsJoker = true;
                Value = "0";
                return;
            }
            else
            {
                Value = initValue.Substring(0, 1);
                Type = GetType(initValue.Substring(1).ToUpper());
            }

        }
        public CardTypes Type { get; set; }
        public string Value { get; set; }
        public bool IsJoker { get; set; }

        public int Score => GetScore();

        private int GetScore()
        {
            return (int)Type * GetValue(Value.ToUpper());
        }

        private int GetValue(string value)
        {
            int result = 0;

            switch (value)
            {
                case "T":
                    result = 10;
                    break;
                case "J":
                    result = 11;
                    break;
                case "Q":
                    result = 12;
                    break;
                case "K":
                    result = 13;
                    break;
                case "A":
                    result = 14;
                    break;
                default:
                    result = Convert.ToInt32(value);
                    break;
            }

            return result;

        }
        private CardTypes GetType(string value)
        {
            CardTypes result = 0;

            switch (value)
            {
                case "C":
                    result = CardTypes.Club;
                    break;
                case "D":
                    result = CardTypes.Diamond;
                    break;
                case "H":
                    result = CardTypes.Heart;
                    break;
                case "S":
                    result = CardTypes.Spade;
                    break;
                default:
                    result = 0;
                    break;
            }

            return result;

        }
    }

Open in new window

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
ǩa̹̼͍̓̂ͪͤͭ̓u͈̳̟͕̬ͩ͂̌͌̾̀ͪf̭̤͉̅̋͛͂̓͛̈m̩̘̱̃e͙̳͊̑̂ͦ̌ͯ̚d͋̋ͧ̑ͯ͛̉Glanced up at my screen and thought I had coded the Matrix...  Turns out, I just fell asleep on the keyboard.
Most Valuable Expert 2011
Top Expert 2015

Commented:
Unit testing is about testing your assumptions of a piece of code. "My Add() method will return 5 if I pass it 2 and 2." Your unit test should be designed to show whether or not that statement is true.

e.g.

[TestMethod]
public void AddMethod__Passed_2_2__Returns_5()
{
    // Arrange
    SystemUnderTest sut = new SystemUnderTest();
    int expected = 5;
    int actual;

    // Act
    actual = sut.Add(2, 2);

    // Assert
    Assert.AreEqual(expected, actual);
}

Open in new window


The test should pass--i.e. AreEqual should not throw an exception--based on the information found in the name of the method, and the actual code of the test. If I'm coming behind you, the first thing I'm going to look at is the name of the method. What is this guy testing? OK, he's testing that the add method returns 5 if he passes 2 and 2. Is that what the code is actually doing? Yes, it is. But I get an exception. 2 plus 2 is 4, not 5. So now I know there's a flaw in the thinking of the test itself. This test is not valid. I know it's not valid because I know that 2 + 2 = 4. So I fix the test.

[TestMethod]
public void AddMethod__Passed_2_2__Returns_4()
{
    // Arrange
    SystemUnderTest sut = new SystemUnderTest();
    int expected = 4;
    int actual;

    // Act
    actual = sut.Add(2, 2);

    // Assert
    Assert.AreEqual(expected, actual);
}

Open in new window


Now when I run the test, I get "green on screen"--the test passed.

The assumption above was that 2 + 2 = 5. But in any reasonable system, and to any reasonable human, we know that is incorrect. We adjust our test to reflect the correct assumption that 2 + 2 = 4.

So that covers determining if the test itself is a good one. What about when the test is good, but we still get a failure.

[TestMethod]
public void UserRegister__Name_Is_Null__Returns_NameRequired_Error()
{
    // Arrange
    SystemUnderTest sut = new SystemUnderTest();
    string name = null;
    int age = 20;
    string email = "test@example.com";
    string expected = "Name is required.";
    string actual;

    // Act
    actual = sut.UserRegister(name, age, email);

    // Assert
    Assert.AreEqual(expected, actual);
}

Open in new window


Say that actual holds the value null. null does not equal "Name is required.", correct? This means that AreEqual will throw an exception (because that's how MSTest signals a test not passing). So there's a problem somewhere. In this kind of scenario, you need to (double-)check that your "Arrange" section of the test is correct. If it is, then you need to look at the code of the thing that's being tested. In this case, the UserRegister method. Tests can be debugged just like your normal code can. You can step through your code while it's under test and examine exactly how the system is working. Using this facility will allow you to track down bugs, often times ones you didn't even know to think about.

So with all of that said, your job with this code is to determine what your assumptions are about the code. If you pass "banana" to SetUpList, what do you assume will happen? If you pass null, what do you assume will happen. Test all of your assumptions, and be prepared to adjust any of your assumptions, or any of the tests themselves, based on the results of your tests.

Author

Commented:
What syntax do you use when the actual and expected results are of type list rather than string or int?
Also the list is not of type string.  It is of type card.  If you don't want to go through the card model above then assume the list is of type object with its own properties.
I can run unit tests when the expected and output values are strings or integers.  My difficulty is when they are more complex eg lists.
I tried CollectionAssert, but couldn't get it to work as the list was of type card.
Glanced up at my screen and thought I had coded the Matrix...  Turns out, I just fell asleep on the keyboard.
Most Valuable Expert 2011
Top Expert 2015
Commented:
Well, you can use any syntax you prefer really. Think about what you want to test.

Should the list be empty?
Assert.IsTrue(list.Count == 0);

Open in new window


Should the list contain the same items that another list contains?
CollectionAssert.AreEqual(expected, actual);

Open in new window


Should the list contain a particular item?
Assert.IsTrue(list.Contains(someItem));
// OR
CollectionAssert.Contains(list, someItem);

Open in new window

JavaScript Best Practices

Save hours in development time and avoid common mistakes by learning the best practices to use for JavaScript.

Author

Commented:
Thanks Kaufmed.  This is the routine being tested.  It assumes that every third character is a comma.  I'd like to pass a string where that is not the case and see the exception.  
How do I do this?
Am I correct to throw a general exception or would another type of exception be better?

        public List<Card> SetUpList(string s)
        {
            
                //string cardsValue = card.Type;

                for (int i = 2; i < s.Length; i += 3)
                {
                    if (s.Substring(i, 1) != ",")
                    {
                        throw new Exception ( "Deck must consist of two digit card followed by comma");
                        
                    }
                }
            List<Card> cards = new List<Card>();
            //cardsValue.Split(',').ToList<string>().ForEach(x => cards.Add(new CardModel(x)));


            foreach (string item in s.ToUpper().Split(',').ToList<string>())
            {
                Card temp = new Card(item);
                bool cardExists = cards.Any(x => x.Value == temp.Value && x.Type == temp.Type);
                if (!cardExists)
                {
                    cards.Add(temp);
                }
                else if (item.Equals("JR", StringComparison.OrdinalIgnoreCase))
                {
                    cards.Add(temp);
                }
                else
                {
                    throw new Exception( string.Format("Duplicate Card Detected : {1} of {0}", temp.Type, temp.Value));
                }
            }
            return cards;

        }

Open in new window

Author

Commented:
I amended the function slightly and came up with this test.  Please let me know if you think it could be improved
        [Test]
        public void SetUpListTest()
        {
            CardApp.Controllers.CardsController ScoreTest1 = new CardApp.Controllers.CardsController();
            List<CardApp.Models.Card> Deck = new List<CardApp.Models.Card>();
            try
            {
                ScoreTest1.SetUpList("2H,2H");
                Assert.Fail(); // raises AssertionException
            }
            catch (System.FormatException ex)
            {
                Assert.Pass();
            }
            catch (System.Exception ex)
            {
                // not the right kind of exception
                Assert.Fail();
            }
            try
            {
                ScoreTest1.SetUpList("2H3C");
                Assert.Fail(); // raises AssertionException
            }
            catch (System.ArgumentException ex)
            {
                Assert.Pass();
            }
            catch (System.Exception ex)
            {
                // not the right kind of exception
                Assert.Fail();
            }
        }

Open in new window

Here is the amended function.

        public List<Card> SetUpList(string s)
        {
            
                //string cardsValue = card.Type;

                for (int i = 2; i < s.Length; i += 3)
                {
                    if (s.Substring(i, 1) != ",")
                    {
                        throw new ArgumentException ( "Deck must consist of two digit card followed by comma");
                        
                    }
                }
            List<Card> cards = new List<Card>();
            //cardsValue.Split(',').ToList<string>().ForEach(x => cards.Add(new CardModel(x)));


            foreach (string item in s.ToUpper().Split(',').ToList<string>())
            {
                Card temp = new Card(item);
                bool cardExists = cards.Any(x => x.Value == temp.Value && x.Type == temp.Type);
                if (!cardExists)
                {
                    cards.Add(temp);
                }
                else if (item.Equals("JR", StringComparison.OrdinalIgnoreCase))
                {
                    cards.Add(temp);
                }
                else
                {
                    throw new FormatException( string.Format("Duplicate Card Detected : {1} of {0}", temp.Type, temp.Value));
                }
            }
            return cards;

        }

Open in new window

Author

Commented:
Thanks.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial