We’re still looking for the source of the bug that caused the ArgumentNullException when the VerifyAddTask unit test is executed in the file T_TodoModel.cs. It’s time to conclude this bug hunt!
Finding the cause of the bug
By managing exceptions and breakpoints, we’ve gotten as far as the code in the InternalAddTask method, and are attempting to find where the exception is coming from:
Seeing this code, we're going to have to go back through InternalAddTask using Step Into (F11
). If the test of the first line fails, it will throw an InvalidOperationException – but it is not the ArgumentNullException that is detected.
It’s possible that the exception is being thrown within the test itself, when inserting into the _tasks field (which is an instance of List<TodoTask>).
How can we remove any doubt without executing or going down into GetTask?
Check that the received task, and the name sent as a parameter, aren't null: the GetTask parameters are not null.
However, by hovering the mouse over the _task field, it seems that its value is null: the name entered as a parameter is not.
So let’s start from this theory and go into the GetTask method using Step Into (F11
):
The editor indicates that _tasks has a value of null as we saw in the calling method. It’s obvious that the call to FirstOrDefault is going to fail because the list on which this method is going to operate will be null: now we know why the ArgumentNullException is going to be thrown! ;)
Fixing the problem
Seeing the TodoModel class implemented (no constructor or _tasks field explicitly initialized), it looks as though the _tasks field wasn't initialized. So we need to add the following code:
Remember: we need to ensure that the fix solves the initial problem. To do this, we rerun the unit test and check that it goes through okay. Unfortunately, this is not the case!
This time, we get an InvalidOperationException exception type. The message indicates that it is not possible to insert the sleep task a second time.
A bug is not fixed until it passes all the tests following a correction. As that is not the case here, we must carry on with our investigation.
Starting a new investigation
How should we start this new investigation?
One way is to look at the code where the exception is thrown, and insert a breakpoint; not on the line, but on the throw using a right-click or the keyboard shortcut F9
:
The debugger will halt execution just before the exception is thrown.
As mentioned in the previous chapter, you might also want to put a breakpoint on the call to CreateTask in the foreach loop:
You can also define a Hit Count conditional breakpoint to pause before executing the fourth iteration (i.e., before the second task with the name sleep added):
When we rerun the unit test, it throws an exception if there is a task with the same name, as you would expect. We need to decide if we should retain this function, modify the unit test, or add another to make the expected behavior official.
Let's recap!
As you can see, an investigation may require several iterations of the search for a bug. Most of the time there are several possible outcomes. However, I suggest that you add unit tests, which makes the behavior of the tested components easier to follow!
The next part of this course will give you a new way of viewing and modifying data that is being manipulated by code. This will help you be more efficient and able to understand the code you maintain - even if you didn't write it. 😎