2

I am trying to unit test a method that throws an exception, and before throwing it has to perform a few tasks, like logging. I am using NSubstitute and can't get my head around this one.

so my test looks like this

[TestMethod]
[ExpectedException(typeof(IOException))]
public void RecordAnalyser_FileReadFailedInFirstAttempt_WarningLogged()
{
    //Arrange
    var fileMock = Substitute.For<IFile>();
    fileMock.ReadLines(Arg.Any<string>()).Throws(new IOException());

    //Act
    var recordAnalyser = new RecordAnalyser(fileMock, logger); //--> throws exception.

    //Assert
    logger.Received(1).Warn(Arg.Any<string>(), Arg.Any<Exception>());
}

Now i want to assert if logger received a warning log, but since the line above sends an exception, and i have an expected exception attribute, test doesn't come to check for assertion.

One dirty code i can think of is to wrap the error statement in a try catch within the test, but its not the neatest.

//Act
try
{
    var recordAnalyser = new RecordAnalyser(fileMock, logger);
}
catch (Exception)
{
    // eat
}

Code under test -

public RecordAnalyser(IFile file, ILoggerService logger)
{
    this.logger = logger;
    try
    {
        names = file.ReadLines(Constants.Names).ToList();
    }
    catch (System.IO.IOException e)
    {
        logger.Error("Names file could not be read.", ex);
      // How do I test above line without a try catch block in unit test 
        throw;
    } 
}

looking for suggestions here.

Muds
  • 4,006
  • 5
  • 31
  • 53
  • 2
    Are you using MSTests or NUnit? – Chetan Apr 10 '18 at 10:12
  • If the goal is to assert something after the exception then you will have to catch the exception in this case. Depending on the test runner you would be able to assert the exception differently – Nkosi Apr 10 '18 at 10:15
  • ms tests with nsubstitute – Muds Apr 10 '18 at 10:16
  • @Nkosi i agree but isnt there a support to get around this ? – Muds Apr 10 '18 at 10:17
  • Not in mstest. xunit and nunit probably – Nkosi Apr 10 '18 at 10:17
  • XUnit example https://stackoverflow.com/a/45017575/5233410 – Nkosi Apr 10 '18 at 10:19
  • no thats not the right example, sorry, what i am trying to do is to assert something that happens in catch block, i can assert for thrown exception in attribute (ref code) – Muds Apr 10 '18 at 10:22
  • Dumb question: why is exception thrown? May be you should ensure that no exception is thrown along happy path execution? – Alexander Powolozki Apr 10 '18 at 10:29
  • lol , welcome to sensible coding practices! exceptions are inevitable when you work with outside systems for ex: reading a file in this case – Muds Apr 10 '18 at 10:30
  • This may be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). You are trying to test/assert multiple things in one test. hence the problem. if the goal was just to test that the exception is thrown then great no try/catch and test would pass. In another test where you want to assert that something happens when the exception is thrown then you will need to catch the exception to allow the test to be exercised to completion. – Nkosi Apr 10 '18 at 10:35
  • Can you show a code you are trying to test. If in the code you have `try.. catch` then everything should work - but you need to remove `ExpectedException` attribute. There are other methods to test of raised exception without attribute in better way. – Fabio Apr 10 '18 at 11:08
  • hey Fabio, thanks for your reply but i am not trying to test for expected exception, i am trying to test for code in catch block, let me get you code wait – Muds Apr 10 '18 at 11:09
  • added code under test – Muds Apr 10 '18 at 11:15
  • @Nkosi thanks for introducing me to this new term :) – Muds Apr 10 '18 at 11:16
  • 2
    Ok - you still throwing exception after logging - that is the reason why you need to have `try.. catch` in the test – Fabio Apr 10 '18 at 11:17
  • yea was looking for a way to ignore received exceptions and just continue with assertions – Muds Apr 10 '18 at 11:19
  • 2
    Even if you were to write your own implementation, the fact is that, based on the design of your code under test, the exception HAS to be caught and handled somewhere otherwise the code will fail at the point where it is thrown. – Nkosi Apr 10 '18 at 11:24

2 Answers2

2

This may be an XY problem.

You are trying to test/assert multiple things in one test. hence the problem.

If the goal was just to test that the exception is thrown then great no try/catch and test would pass.

[TestMethod]
[ExpectedException(typeof(IOException))]
public void RecordAnalyser_Should_FailInFirstAttempt_When_FileRead() {
    //Arrange
    var fileMock = Substitute.For<IFile>();
    fileMock.ReadLines(Arg.Any<string>()).Throws(new IOException());

    //Act
    var recordAnalyser = new RecordAnalyser(fileMock, logger); //--> throws exception.
}

In another test where you want to assert that something happens when the exception is thrown then you will need to catch the exception to allow the test to be exercised to completion and allow assertions to be verified.

[TestMethod]
public void RecordAnalyser_Should_LogWarning_When_FileReadFailedInFirstAttempt() {
    //Arrange
    var fileMock = Substitute.For<IFile>();
    fileMock.ReadLines(Arg.Any<string>()).Throws(new IOException());
    IOException error = null;

    //Act
    try {
        var recordAnalyser = new RecordAnalyser(fileMock, logger); //--> throws exception.
    } catch(IOException ex) {
        error = ex; //catch and hold error for later
    }

    //Assert

    if(error == null)
        Assert.Failed("exception expected"); // error was not thrown.

    logger.Received(1).Warn(Arg.Any<string>(), Arg.Any<Exception>());
}    
Nkosi
  • 235,767
  • 35
  • 427
  • 472
0

You could use following Extension which provides the implementation of Assert.Throws(Action) and Assert.Throws(Action): https://github.com/bbraithwaite/MSTestExtensions