Unfortunately, when it comes to unit testing concurrent code, there is no silver bullet solution that will work for everything. You'll have to use your own discretion in order to come up with a strategy, or strategies, that will work for the systems that you are developing.
You will likely never be able to test every possible scenario of your codebase if you introduce multiple threads. Ideally, you should try to follow a blend of different strategies like these:
- Unit test the parts of your code that don't run on multiple threads.
- Create a test suite that probes your multithreaded code in a variety of different ways. If possible, try and include load tests on these specific sections of code to give you confidence that your multithreaded logic stands up to constant pressure.
There are, of course, more strategies that you can follow, but adequately testing your codebase is a complex blend of both science and art that hugely depends on what you are developing and the complexities of your system.
One rule of thumb I would tend to agree with is that if your code is so complex that testing becomes near impossible, then it could be time to rethink your initial approach, and come up with a simpler design. This isn't always possible, but it is something I would actively recommend you do if you have the time and resources available to do so.