54

I have the method:

public static int Add(List<int> numbers)
    {
        if (numbers == null || numbers.Count == 0)
            return 0;

        if (numbers.Count == 1)
            return numbers[0];


        throw new NotImplementedException();
    }

Here is my test against it, but it does not like new List<int> {1} in the TestCase:

    [TestCase(new List<int>{1}, 1)]
    public void Add_WithOneNumber_ReturnsNumber(List<int> numbers)
    {

        var result = CalculatorLibrary.CalculatorFunctions.Add(numbers);

        Assert.AreEqual(1, result);
    }

It gives me the error:

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

Do I have to do it like this:

    [Test]
    public void Add_WithOneNumber_ReturnsNumber()
    {

        var result = CalculatorLibrary.CalculatorFunctions.Add(new List<int>{7});


        Assert.AreEqual(7, result);

        var result2 = CalculatorLibrary.CalculatorFunctions.Add(new List<int> {3});

        Assert.AreEqual(4,result2);
    }
xaisoft
  • 3,343
  • 8
  • 44
  • 72

8 Answers8

69

There is one option to use TestCaseSource attribute. Here I provide a non-assert test with two cases just to see how it works:

[TestFixture]
public class TestClass
{
    private static readonly object[] _sourceLists = 
    {
        new object[] {new List<int> {1}},   //case 1
        new object[] {new List<int> {1, 2}} //case 2
    };

    [TestCaseSource("_sourceLists")]
    public void Test(List<int> list)
    {
        foreach (var item in list)
            Console.WriteLine(item);
    }
}

Anyhow I have to mention it is not the most evident solution and I would prefer neatly organized fixtures ignoring the fact they are more verbose

More information: https://github.com/nunit/docs/wiki/TestCaseSource-Attribute

Welcor
  • 2,431
  • 21
  • 32
Yurii Hohan
  • 4,021
  • 4
  • 40
  • 54
  • Can you clarify your last comment with an example? And is it possible to actually call another test from another test or is this bad practice? – xaisoft Oct 20 '13 at 19:11
  • By the way, this worked, but I do agree it is a little strange. I probably would rather have more concise and neat tests. – xaisoft Oct 20 '13 at 19:16
  • I don't understand the value of creating a `private` array of lists in the class, only to reference them via an attribute. Not trying to be rude, just not understanding why this is any better than just having no parameters to the `Test()` method and generating the list in the test method itself. Does this `list` get re-used in other test methods? If so, then wouldn't a `Setup()` type method be more useful or no? – Karl Anderson Oct 22 '13 at 02:18
  • It is not better which I state in the answer, it is just the way of doing it that the library allows – Yurii Hohan Oct 22 '13 at 06:15
  • `_sourceLists` must be public not private. – Keith Jun 30 '14 at 03:33
  • @Keith why do you say that? It works fine with _sourceLists being private. – namford Aug 24 '17 at 11:30
  • 3
    Update for your comment, use: private static readonly object[] _Data = { new object[] {new List {0}, "test"}, new object[] {new List {0, 5}, "test this"}, }; [Test, TestCaseSource(nameof(_Data))] – zquanghoangz Oct 11 '18 at 07:05
  • for a list i would use my answer instead. But this answer is great for using other objects in TestCases! – Welcor Jan 25 '20 at 12:14
29

My solution is simpler, I just use params. I hope this works for you!

[TestCase(1, 1)]
[TestCase(10, 5, 1, 4)]
[TestCase(25, 3, 5, 5, 12)]
public void Linq_Add_ShouldSumAllTheNumbers(int expected, params int[] numbers)
{
    var result = CalculatorLibrary.CalculatorFunctions.Add(numbers);
    Assert.AreEqual(expected, result);
}
Jaider
  • 14,268
  • 5
  • 75
  • 82
11

Improve code for @Yurii Hohan answer:

private  static readonly object[] _Data =
        {
            new object[] {new List<int> {0}, "test"},
            new object[] {new List<int> {0, 5}, "test this"},
        };

[Test, TestCaseSource(nameof(_Data))]

Hope this help.

zquanghoangz
  • 663
  • 5
  • 11
9

I often use strings and parsing as it renders nicely in the testrunner. Sample:

[TestCase("1, 2")]
[TestCase("1, 2, 3")]
public void WithStrings(string listString)
{
    var list = listString.Split(',')
                         .Select(int.Parse)
                         .ToList();
    ...
}

Looks like this in Resharper's runner:

enter image description here

Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
7

You can use this :

[TestCase(new []{1,2,3})]
public void Add_WithOneNumber_ReturnsNumber(int[] numbers)
Vaccano
  • 78,325
  • 149
  • 468
  • 850
jecaestevez
  • 119
  • 1
  • 3
  • That's not a list, it's an array. OP would have to convert this to a list. – LarryBud Dec 04 '18 at 15:33
  • 1
    In my opinion this is the best approach. Yes this is not List but you can easily convert to List in test source code if you need. This is the way I do when I need to pass List to test. – Vlad Jan 19 '23 at 13:50
5

use array as parameter new [] {1, 2} for the Testcases and convert it to List inside the test method numbers.ToList().

using System.Linq
...

[TestCase(new [] {1}, 1)]
[TestCase(new [] {1, 2}, 3)]
[TestCase(new [] {1, 2, 3}, 6)]
public void Return_sum_of_numbers(int[] numbers, int expectedSum)
{
    var sum = CalculatorLibrary.CalculatorFunctions.Add(numbers.ToList());

    Assert.AreEqual(expectedSum, sum );
    // much cooler with FluentAssertions nuget:
    // sum.Should.Be(expectedSum);
}
Welcor
  • 2,431
  • 21
  • 32
-2

You can't use objects only compile-time constants in data attributes. To avoid using reflection, which I find to be extremely unreadable and not at all appropriate for a test which is meant to formally describe behavior as clearly as possible, here's what I do:

    [Test]
    public void Test_Case_One()
    {
        AssertCurrency(INPUT, EXPECTED);
    }

    [Test]
    public void Test_Case_Two()
    {
        AssertCurrency(INPUT, EXPECTED);
    }

    private void AssertScenario(int input, int expected)
    {
        Assert.AreEqual(expected, input);
    }

It's a few more lines, but that's only because I want clear test output. You could just as easily put them in one [Test] if you are looking for something more concise.

Timothy Gonzalez
  • 1,802
  • 21
  • 18
-4

Just create the list inside the method instead, like this:

public void Add_WithOneNumber_ReturnsNumber()
{
    var result = CalculatorLibrary.CalculatorFunctions.Add(new List<int>{1});

    Assert.AreEqual(1, result);
}
Karl Anderson
  • 34,606
  • 12
  • 65
  • 80
  • Does this mean that I have to have to have a separate test for 3 and 7 and can't use TestCase? – xaisoft Oct 20 '13 at 16:33
  • If `3` and `7` test different things, then yes you will need to have different tests cases for each. If they are just two values that both exercise the same test, then no. – Karl Anderson Oct 20 '13 at 16:35
  • How can I include 3 and 7 in my Test? The only way I can think is to instantiate a List for each number and call the Add Method, so basically, my Test is running like a TestCase. Do I have that correct? – xaisoft Oct 20 '13 at 16:49
  • Is my only option the new Test I posted? – xaisoft Oct 20 '13 at 16:53