11

How would I write a unit test to ensure that a lock was acquired?

For example:

public void AddItem(object item)
{
    lock (_list)
    {
        _list.Add(item)
    }
}

Is there a way to ensure that the lock statement is run?

Edit: I'm not trying to prove thread-safety (this I would assume), just that the lock() statement is called.

For example, I might test a new object() statement by substituting it in a CreateObject() factory function.

g t
  • 7,287
  • 7
  • 50
  • 85
  • 10
    Certain things you take on faith. I wouldn't write a unit test for 1+1=2, and I wouldn't write a test to explicitly check if the list is locked. What is the underlying concept you're trying to validate by a test like this? – Joe May 30 '12 at 13:13
  • Only if you can intercept that call to `Add`. Is `_list` something that is constructed by the test/framework and passed into the code under test? – Damien_The_Unbeliever May 30 '12 at 13:15
  • @Damien_The_Unbeliever Yes, I could easily mock _list. – g t May 30 '12 at 13:20
  • @Joe This is a very simple example, I'm really wondering if such a test would be possible. I can't see a huge use for it, other than ensuring in future the lock is not accidentally removed or bypassed. – g t May 30 '12 at 13:22
  • 1
    @gt that wouldn't be unit testing the Lock, that would be testing for side-effects in other code. Unit testing the Lock would be ensuring it does what it should do. Unit testing isn't really set up for testing side-effects (e.g. you could have a function that squares numbers but also formats USB drives as a side-effect, you'd probably only programatically verify it was squaring correctly as a Unit test). – joocer May 30 '12 at 14:07
  • It might be useful you had another method that was called with the assumption that a lock was already held. You might then want to do `Debug.Assert(IsLockHeld(monitor))` to indicate the intention and have it checked in debug builds, but avoid the potential performance costs of lock re-entry (although I have no idea if that would be enough to make this worthwhile). – SimonC Jun 01 '12 at 05:00

6 Answers6

9

Unit-testing multiple threads is always tricky and should be approached with care.

In your case I wouldn't bother testing lock keyword, for the same reason you don't write tests for new.

Assert.IsNotNull(new object());

Also, you seem to be encapsulating as thread-unsafe collection in order to make it thread-safe. Instead of reinventing the wheel consider using thread-safe collections.

Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • Good idea with the thread-safe collections, I need to remove items at random positions and I'm not sure that's possible though. – g t May 30 '12 at 13:37
4

The same way you test any other thing: write a test that fails without it. In other words, determine why you are writing the lock in the first place, and write a test that specifies that reason. I assume the reason is thread-safety: if so, write the multi-thread test. If it is some other reason, right that test.

If you really, really want to test calling the lock but not the behavior the lock causes, encapsulate:

public class MyLock : IDisposable
{
    private object _toLock;

    public MyLock(object toLock)
    {
        _toLock = toLock;
        Monitor.Enter(_toLock);
    }

    public virtual void Dispose()
    {
        Monitor.Exit(_toLock);
    }
}

You'll have to make a mockable factory too, of course. Seems like overdoing it to me, but maybe it makes sense in your context.

tallseth
  • 3,635
  • 1
  • 23
  • 24
  • Would that be entirely equivalent to a lock()? This could be what I was looking for - I could then mock MyLock and verify it was being created. – g t Jun 01 '12 at 06:42
  • 1
    Not _entirely_, but close enough, see this question: http://stackoverflow.com/questions/2837070/lock-statement-vs-monitor-enter-method. Also, see this question for more deails on syntax across c# versions: http://stackoverflow.com/questions/4978850/monitor-vs-lock – tallseth Jun 01 '12 at 10:18
4

See Jon Skeet's answer to a similar question: How to test if a thread is holding a lock on an object in C#?:

I don't believe there is. There are grotty hack things you could do like calling Monitor.Wait(monitor, 0) and catching the SynchronizationLockException, but that's pretty horrible (and could theoretically "catch" a pulse that another thread was waiting for). EDIT: In .NET 4.5, this is available with Monitor.IsEntered.

Colin
  • 22,328
  • 17
  • 103
  • 197
SimonC
  • 6,590
  • 1
  • 23
  • 40
1

If you've written the Lock statement (which does something similar the the System.Threading.Lock statement) then I can see why you'd want to test it.

In this scenario you'd need to have a _list class that you've implemented the .Add method for this class, this will be a lot easier if you're using Dependency Injection to inject an IList to set _list. You would need to use a dummy instance of the IList which implements the .Add() method.

If you have the dummy .Add() call sleep for a set period of time (say 5 seconds), you could test by, starting a thread to call the .AddItem() method, this thread will lock the .Add() call via the .AddItem() method, the main thread could then wait 3 seconds before calling the .AddItem method.

If the lock works, the second thread will be delayed 2 seconds before executing the .Add, if the lock doesn't work, it will call straight-away.

It's messy and non-deterministic so if you run enough times (millions) you'll get a false result.

joocer
  • 1,111
  • 1
  • 7
  • 11
0

You need a small program where you emulate the reading and writing with threads...

In a new thread create 10,000 items and add them, while in another thread reading and then sleeping for some time.

Then in the main wait for both threads to exit and you should have the results according to the amount of times the test called add and did not call remove...

E.g when i % 2 == 0 then remove instead of add in the producer threads... this should also effect the consumer thread...

see How to wait for thread to finish with .NET?

Community
  • 1
  • 1
Jay
  • 3,276
  • 1
  • 28
  • 38
0

Not sure that unit testing the lock is the right approach, rather you should test that AddItem functions correctly. Couple of things you could do, however:

1) Use reflection to obtain a reference to _list, lock it in your test, and try to call AddItem

2) Add an internal method for testing that locks _list and pauses for a reasonable amount of time (2 seconds?). If you make your internals visible to your test assembly you can call the test method on one thread and call AddItem on another. Keep in mind that it is not fool-proof as technically the test could be blocked for some reason long enough for the initial lock to expire.

D Stanley
  • 149,601
  • 11
  • 178
  • 240