4

I have the following code

public class MyCollection<T>
{
    public void Add(T obj) { ... }

    public int Count { get; }
}

and the following unit test to check whether Add increases the Count property by one:

[Fact]
public void Add_ShouldIncreaseCount()
{
    var collection = new MyCollection<int>();
    var oldCount = collection.Count;

    collection.Add(default);

    Assert.Equal(collection.Count, oldCount + 1);
}

However, I feel like the test is not "good" as in it only shows that Add works for MyCollection<int>.

Do I have to add tests for more value types and also reference types (e.g. string etc.) or is there a way (such as AutoFixture's fixture.Create<T>() to create arbitrary values) to create a "dummy" type?

Is there a way to indicate that I do not care about the actual generic type? What are the best practices in this case?

  • 1
    While TDD isn't the be all and end all, I'd say it is relevant here: don't worry about it unless you actually have a failing test case. Do you have any reason to believe the behaviour would be different for different types? – Philip Kendall Dec 21 '20 at 13:04
  • Unless your function goes out of its way to determine the concrete type, your code doesn't care either. It *can't* do anything with an argument of type `Foo` that it couldn't do with an argument of type `int` as well. – chepner Dec 21 '20 at 21:48
  • 1
    Generic types generally behave slightly differently depending on whether `T` is a reference type (`where T : class`) or a value type (`where T : struct`) like `int`. Try testing it for another type that's a reference type and call it a day. – Flydog57 Dec 21 '20 at 22:12

2 Answers2

6

Testing the degree of freedom that a generic class affords you is like testing a function that can take any int. There's no reasonable way to test every possible input, and no construct that I know of to ensure that the same test would succeed no matter what T is.

Therefore, you should do what you do with a normal function of int: you test a few different int values (in particular, boundary cases to test for off-by-one errors and such). Here this means that you test the Add() with an int instantiation, with a string instantiationr and perhaps with some totally other type. For instance, if your language has function types you might use that to demonstrate that your container really just stores the function, as opposed e.g. to calling it. Other than that I don't see what else due diligence would require.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • This question is C# specific, but if this were C++ I'd also add test that the container obeys move or copy semantics as appropriate. C# sidesteps the need for this test, luckily. – Mooing Duck Dec 21 '20 at 20:49
  • 1
    Yeah, but when testing something that can be any int, picking `0`, `1` and something else(say `42`) is often a good idea – Flydog57 Dec 21 '20 at 22:13
0

I would test with object first, it's the highest level thing you can use. Also it's the responsibility of classes that inherit object to conform to object, so if MyType encounters issues, it's more likely an issue with the type than this code. I would add tests for the most common anticipated implementations if you really want peace of mind, it's not that expensive to do. A general side note, I personally prefer magic numbers in unit tests, because it could potentially mask issues to use count to get the initial value.

Ryathal
  • 13,317
  • 1
  • 33
  • 48