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

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

AlHal2Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

kaufmed   ( ͡° ͜ʖ ͡°)*whispers*  I C# people.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.
AlHal2Author 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.
kaufmed   ( ͡° ͜ʖ ͡°)*whispers*  I C# people.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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Introduction to Web Design

Develop a strong foundation and understanding of web design by learning HTML, CSS, and additional tools to help you develop your own website.

AlHal2Author 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

AlHal2Author 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

AlHal2Author Commented:
Thanks.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
C#

From novice to tech pro — start learning today.