2009 Advent Calendar December 2nd
Adding virtual methods to handle locks as we did yesterday is really not a good solution. The lock should be a separate object and in order to be able to fake the lock I'll make it an interface:
1: public interface Lock
2: {
3: void Lock();
4: void Unlock();
5: }
6:
7: public class MutexLock : Lock
8: {
9: private readonly Mutex _lock = new Mutex();
10:
11: public void Lock()
12: {
13: _lock.WaitOne();
14: }
15:
16: public void Unlock()
17: {
18: _lock.ReleaseMutex();
19: }
20: }
Considering that it would be pretty neat to inject the lock dependency to the important object using generics:
1: public class ImportantObject<Tlock> where Tlock : Lock,new()
2: {
3: private readonly Tlock _lock = new Tlock();
4:
5: public void ImportantMethod()
6: {
7: _lock.Lock();
8: // Do things.
9: _lock.Unlock();
10: }
11: }
Then thread safety can be tested like this:
1: public class Given_an_ImportantObject
2: {
3: class FakeLock : Lock
4: {
5: public static int NumberOfLocks { get; private set; }
6:
7: public FakeLock()
8: {
9: NumberOfLocks = 0;
10: }
11:
12: public void Lock()
13: {
14: ++NumberOfLocks;
15: }
16:
17: public void Unlock()
18: {
19:
20: }
21: }
22:
23: private ImportantObject<FakeLock> _importantObject = new ImportantObject<FakeLock>();
24:
25: [Fact]
26: void It_should_take_lock_when_ImportantMethod_is_called()
27: {
28: _importantObject.ImportantMethod();
29: Assert.Equal(1, FakeLock.NumberOfLocks);
30: }
31: }
However since generics are used we need a static spy in the faked lock which might be a problem if several tests run concurrently. Not typical for unit test runners but not impossible. We'll have to address that later.