I'm writing a Job class, and to ensure that this job can only be executed once, I have introduced a custom "Locking" mechanism.
The function looks like this:
public async Task StartAsync(CancellationToken cancellationToken)
{
if (this.@lock.IsLocked())
{
return;
}
this.@lock.Lock();
await this.ExecuteAsync(new JobExecutionContext(cancellationToken))
.ConfigureAwait(false);
this.@lock.Unlock();
}
Now, when I write tests, I should test the external observable behavior, rather than testing implementation details, so I have the following tests at the moment:
[Theory(DisplayName = "Starting a `Job` (when the lock is locked), does NOT execute it.")]
[AutoDomainData]
public async Task StartingWithLockedLockDoesLockNotExecuteIt([Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = lockMock.Setup(x => x.IsLocked())
.Returns(true);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
jobMock.Verify(job => job.ExecuteAsync(It.IsAny<IExecutionContext>()), Times.Never);
}
[Theory(DisplayName = "Starting a `Job` (when the lock is NOT locked), does lock the lock.")]
[AutoDomainData]
public async Task StartingWithNotLockedLockDoesExecuteIt([Frozen] Mock<ILock> lockMock,
[Frozen] Mock<Job> jobMock)
{
// VALIDATION.
ILock @lock = lockMock?.Object ?? throw new ArgumentNullException(nameof(lockMock));
Job job = jobMock?.Object ?? throw new ArgumentNullException(nameof(jobMock));
// MOCK SETUP.
_ = lockMock.Setup(x => x.IsLocked())
.Returns(false);
// ACT.
await job.StartAsync(new CancellationToken())
.ConfigureAwait(false);
// ASSERT.
jobMock.Verify(job => job.ExecuteAsync(It.IsAny<IExecutionContext>()), Times.Once);
}
Note: I'm using AutoFixture
, but I left the boilerplate code out.
Now, I have the following cases covered:
- When the lock is locked, the job is NOT executed.
- When the lock is NOT locked, the job is executed.
But I'm missing the following important case:
- Guarantee, that during the duration of the exceution, the lock is active.
How can I properly test this? I have the feeling that the design should be updated, but I don't exactly know how.
Any advice?