6
gitpush
6y

How do you test unreachable code or part that is considered an edge case?

For example I catch exceptions in case IO failed and data was not written on database, but that only happens if hardware failure, or no disk space left, how do I mimic that?

I also have unreachable code for example, in one layer I fetch data (lets call it function x) and always return success result unless item not found I throw KeyNotFound exception. But in the calling function I handle the case of Status == Failed
Just in-case in the future I change function x and start returning failed status, so my logic already written but never reachable

Comments
  • 2
    Leaving this comment to see future answers!
    📌
  • 2
    In test driven development you're supposed to change/write your codebase in such a way that it IS possible to test.

    For example by passing parameters to the class and mocking these in your tests
  • 0
    @Tobyvw but doesn't this introduce params that are not required, for example why would my production code have a param that says: isMock (or whatever that helper param is called)
  • 3
    In a unit test you would do this by making sure the actual function f is never called from the calling function, by using inversion of control (for example, dependency injection), and swapping the function f with a function that always returns the value (or throws the exception) for the situation you are testing. This is called a mock function (other relevant terms: stub, spy).

    You basically say:
    If the function f returns "failed"
    ..(by replacing the function f with an actual function { return "failed"; })

    ...then I expect my program (the calling function) to behave in some way.

    This way, it does not matter the functionality hasn't been implemented yet in your actual dependency; when it does (presumably because you will be coding that next), your function will be ready for it.
  • 2
    @cannonau This is what I meant.

    Mocking is not the art of creating an extra variable, it's "faking" an object in your test.
  • 1
    If you doing unit tests, you probably shouldn't have IO anyway, you use mocks (and get your mock to throw in your case).

    I usually prefer mocks rather than using dependency injection, as the latter leads to using factories or this kind of pattern that adds indirection. Plus, with DI you still have to mock the dependencies you want to inject (even if they aren't used, as they are required by constructor).
  • 1
    @willol how do you get the code to use your mock instead of the real thing?
  • 1
    Generally, testing for something that will only go wrong under very rare circumstances might just be too much effort (considering the actual benefit of that particular test).
    So instead, maybe simply review the error handling code once more and call it a day.
    There will almost certainly be more critical tests to write ;)
  • 1
    @cannonau with mocking libraries, like unittest.mock in Python or jest.mock in JS
  • 0
    @Tobyvw @cannonau @losdanielos @willol
    I still don't understand, how do I test for this:

    try
    {
    sendEmail()
    }catch(ex){
    // Email not sent
    }

    How do I test code in catch block? When my function takes only title and body and to as params
  • 1
    If sendMail is a function in a different class, mock the Object of that class.

    If sendMail is a function in the same class, it's a tad more difficult, and to fully comply to tdd, you probably need to refactor. While you could try to mock sendMail()
    (Using PowerMock in Java iirc), this is considered bad practice.
  • 0
    @Tobyvw Ok point is clear now, thanks man I'll read more about it. Happy new year :)
  • 0
    Some ways to test for unreachable code or extreme cases:

    Fault simulation: To simulate I/O errors or lack of disk space, you can use fault simulation tools such as fault injection frameworks or hardware emulators.

    Create test scenarios: Create test cases to catch exceptions and unreachable conditions. For example, you can write test cases to raise exceptions and ensure that the code handles them properly.

    Code coverage: Use code coverage tools to identify untested parts of code and focus on them.

    Check for branches that cannot be reached: Check for branches of code that cannot be accessed to ensure that they do not affect the main logic.

    Check for extreme cases: Check for extreme cases like invalid input values, system errors, etc. to ensure that the application handles them appropriately.
Add Comment