0

I have a class ProcessingQueue that has an void Enqueue(Item item) method and also implements IDisposable. The Dispose() method of the class guarantees that once it returns, all enqueued items are processed.

How to unit test this guarantee?

If in my unit test I just call Dispose() and then check that all the items were processed, it means nothing, because I was probably just lucky and all the items were processed even before Dispose() was called.

yaskovdev
  • 1,213
  • 1
  • 12
  • 22
  • 1
    Does this answer your question? [Should Dispose methods be unit tested?](https://stackoverflow.com/questions/3259456/should-dispose-methods-be-unit-tested) specifically https://stackoverflow.com/a/3260218/4800344 – Ermiya Eskandary Oct 12 '21 at 10:06
  • 2
    Can you mock processing logic and add some kind of wait or synchronisation there to guarantee that mocked processing will not start before you called Dispose? – Serg Oct 12 '21 at 10:17
  • 5
    You can't prove the absence of a threading race bug with a unit test. Gain confidence with an extended test that runs for days, using variable machine load (start extra threads) and variable timing (delay with random for-loops). Still doesn't prove absence, but lowers the odds for surprises. – Hans Passant Oct 12 '21 at 11:08
  • @HansPassant, got it, there is no way to 100% prove correctness of such program. But is it at least possible to write a test that proves, say, only one scenario so that the test would not rely on delays, waits and any other timings? – yaskovdev Oct 20 '21 at 08:10
  • 1
    No, random timing is crucial to flush out concurrency bugs. A decent video about this kind of testing is [here](https://channel9.msdn.com/shows/Going+Deep/CHESS-An-Automated-Concurrency-Testing-Tool/). – Hans Passant Oct 20 '21 at 09:06

1 Answers1

2

Unit testing multithreaded code is fraught with difficulty if you want a deterministic behavior of the test. Usually involving lots of ManualResetEvent to control the flow of control.

It should still be possible however, assuming you can control the processing inside the item. For example

myProcessingQueue.Enqueue(() => releaseTask.WaitOne());

var disposeTask = Task.Run(() => myProcessingQueue.Dispose());

// the dispose task should not complete since
// the task is blocked on releaseTask.WaitOne()
Assert.IsFalse(disposeTask.Wait(100));  

// Release the task to allow the dispose method to complete
releaseTask.Set();

// wait to ensure the dispose actually does complete
Assert.IsTrue(disposeTask.Wait()); 

This is not a total guarantee, since it might be possible for the disposeTask to have (incorrectly) completed after 101ms instead of 100ms, but I'm not sure how, or even if, that problem can be fixed. But I hope this approach might be usefull in at least some cases.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thank you for the answer! I have got the idea. However, isn't relying on timings the guaranteed way to end up with flaky tests? As you mentioned, there is always a probability for the timings to not lead to a sequence of calls you expect. Even if such probability is, say, 0.000001, if you have 1000 such tests and you run them 1000 times a week, you will have the unexpected test fail once a week in average. – yaskovdev Oct 20 '21 at 08:02
  • 1
    @yaskodev, yes, relying on timings *can* lead to flaky tests. In this specific example the timing dependency can only lead to false negatives, i.e. a test that should not pass but does so. In the end you have to decide what is worth having unit tests for or not. – JonasH Oct 20 '21 at 09:06