1

I'm a big fan of TestCases because they make it trivial to test more edge cases, and the anti-testers seem happier with fewer lines of testing code. I've always struggled with exception testing though.

In my code I have a validation function that checks if there is 1 and only 1 instance in the config. If there are 0 I throw a KeyNotFoundException, and if there's more than 1 a AmbiguousMatchException.

private void ThrowIfNotInConfig(string endpointRef)
{
    var count = _config.Endpoints?.Count(x => x.Ref.Equals(endpointRef)) ?? 0;
    if (count == 0)
    {
        throw new KeyNotFoundException($"{nameof(ThrowIfNotInConfig)} - " +
                                       $"No Endpoint in appSettings with the ref {endpointRef}");
    }
    if (count > 1)
    {
        throw new AmbiguousMatchException($"{nameof(ThrowIfNotInConfig)} - " +
                                      $"More than 1 Endpoint in appSettings with the ref {endpointRef}");
    }
}

Is there a way I can avoid splitting these into 2 separate tests, just because of the exception type, that's neater than this? I don't like doing a try catch in a test, and I feel there should be a way to test against the Exception, like ExpectedException used to.

[TestCase(0, ExpectedResult = "KeyNotFoundException")]
[TestCase(2, ExpectedResult = "AmbiguousMatchException")]
public string ThrowIfNotInConfig_GIVEN_NotASingleEndpointInConfig_THEN_ThrowError(int configOccurrences)
{
    // Arrange
    const string endpointRef = "ABC";
    var config = _validConfig;
    config.Endpoints.RemoveAll(x => x.Ref == endpointRef);
    config.Endpoints
        .AddRange(Enumerable.Range(0, configOccurrences)
            .Select(x => new MiraklEndpointConfig { Ref = endpointRef })
        );
    _config.Setup(c => c.Value).Returns(config);

    var service = new URLThrottlingService(_mockLogger.Object, _config.Object);
    
    try
    {
        // Act
        service.IsOKToCallEndpoint(endpointRef);
    }
    catch (Exception exception)
    {
        return exception.GetType().Name;
    }

    return "";
}
Red
  • 3,030
  • 3
  • 22
  • 39
  • 3
    I'd probably make the method accept a `Type exceptionType`, then use `Assert.Throws(exceptionType, () => ...)` inside the test method, and pass in `typeof(KeyNotFoundException)` as a parameter from the `TestCase` attribute – canton7 Sep 14 '21 at 13:02
  • 1
    See also https://stackoverflow.com/questions/35061336/testing-for-exceptions-with-testcase-attribute-in-nunit-3 – canton7 Sep 14 '21 at 13:04
  • Thanks @canton7, but that link only tests that a particular exception type occurs. I'm trying to test that exception type x occurs when y is the value, as set by the test case – Red Sep 14 '21 at 13:08
  • 1
    I'm referring specifically to [Oliamster's answer](https://stackoverflow.com/a/65090607/1086121) – canton7 Sep 14 '21 at 13:09
  • I see. I'm not a fan of the logic in the test, but when you strip that out that is indeed a neater solution. I'll add an answer so I can share the code – Red Sep 14 '21 at 13:18

1 Answers1

3

Thanks to canton7 and Olimasters answer, I now have this, which is what I was after

[TestCase(0, typeof(KeyNotFoundException))]
[TestCase(2, typeof(AmbiguousMatchException))]
public void ThrowIfNotInConfig_GIVEN_NotASingleEndpointInConfig_THEN_ThrowError(int configOccurrences, Type exception)
{
    // Arrange
    const string endpointRef = "ABC";
    var config = _validConfig;
    config.Endpoints.RemoveAll(x => x.Ref == endpointRef);
    config.Endpoints
        .AddRange(Enumerable.Range(0, configOccurrences)
            .Select(x => new MiraklEndpointConfig { Ref = endpointRef })
        );
    _config.Setup(c => c.Value).Returns(config);

    var service = new URLThrottlingService(_mockLogger.Object, _config.Object);

    // Act / Assert
    Assert.Throws(exception, () => service.IsOKToCallEndpoint(endpointRef));
}
Red
  • 3,030
  • 3
  • 22
  • 39