I'm trying to test a really simple function. It returns numbers which contain some specified digit. If the first argument is null
, it'll throw ArgumentNullException
.
Unfortunately, Assert.Throws
says, that the expected exception isn't thrown and the test fails. When I'm trying to debug the test, it doesn't step into my method. The same thing with ArgumentException
.
Only the two last tests fail, others are successful.
My function to be tested:
/// <summary>
/// Filter given numbers and return only numbers containing the specified digit.
/// </summary>
/// <param name="numbers">The numbers to be filtered.</param>
/// <param name="digit">The digit which should be found.</param>
/// <returns>Numbers that contains the digit.</returns>
/// <exception cref="ArgumentException"> Thrown if the digit value isn't between 0 and 9.</exception>
/// <exception cref="ArgumentNullException"> Thrown if numbers are null.</exception>
public static IEnumerable<int> FilterDigits(IEnumerable<int> numbers, byte digit)
{
if (numbers == null)
{
throw new ArgumentNullException();
}
foreach (int number in numbers)
{
if (number.ContainsDigit(digit))
{
yield return number;
}
}
}
/// <summary>
/// Check whether the number contains the given digit.
/// </summary>
/// <param name="number">The number which can contain the digit.</param>
/// <param name="digit">The digit to be found.</param>
/// <returns>True if the number contains the digit, else false.</returns>
/// <exception cref="ArgumentException"> Thrown if the digit value isn't between 0 and 9.</exception>
/// <example> ContainsDigit(10, 1) -> true </example>
/// <example> ContainsDigit(10, 2) -> false </example>
private static bool ContainsDigit(this int number, byte digit)
{
if (!char.TryParse(digit.ToString(), out char digitChar))
{
throw new ArgumentException("The digit should be from 0 to 9.");
}
string numberString = number.ToString();
foreach (char ch in numberString)
{
if (ch == digitChar)
{
return true;
}
}
return false;
}
My test class:
[TestFixture]
public class DigitsFilterTests
{
[TestCase(new int[] { 1, 4, 23, 346, 7, 23, 87, 71, 77 }, 7, ExpectedResult = new int[] { 7, 87, 71, 77 })]
[TestCase(new int[] { 345, 4, 0, 90, 709 }, 0, ExpectedResult = new int[] { 0, 90, 709})]
public IEnumerable<int> FilterDigits_NumbersContainDigit(int[] numbers, byte digit)
=> DigitsFilter.FilterDigits(numbers, digit);
[TestCase(new int[] { 1, 4, 222, 9302 }, 7, ExpectedResult = new int[] { })]
[TestCase(new int[] { 345, 4, 354, 25, 5 }, 0, ExpectedResult = new int[] { })]
public IEnumerable<int> FilterDigits_NumbersNotContainDigit(int[] numbers, byte digit)
=> DigitsFilter.FilterDigits(numbers, digit);
[TestCase(new int[] { }, 0, ExpectedResult = new int[] { })]
public IEnumerable<int> FilterDigits_EmptyList(int[] numbers, byte digit)
=> DigitsFilter.FilterDigits(numbers, digit);
[Test]
public void FilterDigits_NullNumbers_ArgumentNullException()
=> Assert.Throws<ArgumentNullException>(() => DigitsFilter.FilterDigits(null, 5));
[Test]
public void FilterDigits_InvalidDigit_ArgumentException()
=> Assert.Throws<ArgumentException>(() => DigitsFilter.FilterDigits(new int[] { }, 10));
}
Your method is an enumerable built using yield return
. What's tricky about it is that nothing will actually happen unless you enumerate it.
So you must make sure that your test enumerates the contents:
[Test]
public void FilterDigits_NullNumbers_ArgumentNullException()
=> Assert.Throws<ArgumentNullException>(() => DigitsFilter.FilterDigits(null, 5).ToList());
Also, your second test will fail either way because you won't reach ContainsDigit
if numbers
is empty.
If you want to fix the behavior inside of the method, you need to cut it into two:
public static IEnumerable<int> FilterDigits(IEnumerable<int> numbers, byte digit)
{
if (numbers == null)
{
throw new ArgumentNullException();
}
return FilterDigitsImpl(numbers, digit);
}
private static IEnumerable<int> FilterDigitsImpl(IEnumerable<int> numbers, byte digit)
{
foreach (int number in numbers)
{
if (number.ContainsDigit(digit))
{
yield return number;
}
}
}
If your version of C# is recent enough, you can merge both methods using local functions:
public static IEnumerable<int> FilterDigits(IEnumerable<int> numbers, byte digit)
{
if (numbers == null)
{
throw new ArgumentNullException();
}
IEnumerable<int> FilterDigitsImpl()
{
foreach (int number in numbers)
{
if (number.ContainsDigit(digit))
{
yield return number;
}
}
}
return FilterDigitsImpl();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With