Best way to unit test cancellation
Good evening. I have been trying to write a unit test that will test the cancellation of a method. Now I have a unit test written that passes, but I am do not think it is what I am looking to achieve. I have tried a few different ways to achieve the cancellation, but most have failed with Test method did not throw expected exception System.OperationCanceledException.
So the method I am trying to Unit test is:
public async Task AddToDatabaseAsync(ApplicationRoles role, string databaseName, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (role == null) throw new ArgumentNullException(nameof(role));
if (databaseName == null) throw new ArgumentNullException(nameof(databaseName));
await _applicationRole.AddToDatabaseAsync(role, databaseName);
}
Here is the Unit test that continually passes:
[TestClass]
public class ApplicationRoleStoreTests
{
private MockRepository _mockRepository;
private Mock<IApplicationRoleDataManager> _mockApplicationRoleDataManager;
[TestInitialize]
public void TestInitialize()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_mockApplicationRoleDataManager = _mockRepository.Create<IApplicationRoleDataManager>();
}
private ApplicationRoleStore CreateApplicationRoleStore()
{
return new ApplicationRoleStore(
_mockApplicationRoleDataManager.Object);
}
[DynamicData(nameof(GetValidDataDatabasesExistWithDatabaseName), DynamicDataSourceType.Method)]
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task AddToDatabaseAsync_RoleWithDatabaseName_Cancellation(ApplicationRoles roles, string databaseName)
{
// Arrange
var applicationRoleStore = CreateApplicationRoleStore();
CancellationTokenSource cts = new();
CancellationToken cancellationToken = cts.Token;
_mockApplicationRoleDataManager.Setup(x => x.AddToDatabaseAsync(roles, databaseName))
.Returns(Task.CompletedTask);
// Act
cts.Cancel();
await applicationRoleStore.AddToDatabaseAsync(
roles,
databaseName,
cancellationToken);
// Assert
_mockRepository.VerifyAll();
}
}
Here is what I have tried in getting it to fail if there is a delay:
Attempt 1 (Adding a callback before the return):
[TestClass]
public class ApplicationRoleStoreTests
{
private MockRepository _mockRepository;
private Mock<IApplicationRoleDataManager> _mockApplicationRoleDataManager;
[TestInitialize]
public void TestInitialize()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_mockApplicationRoleDataManager = _mockRepository.Create<IApplicationRoleDataManager>();
}
private ApplicationRoleStore CreateApplicationRoleStore()
{
return new ApplicationRoleStore(
_mockApplicationRoleDataManager.Object);
}
[DynamicData(nameof(GetValidDataDatabasesExistWithDatabaseName), DynamicDataSourceType.Method)]
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task AddToDatabaseAsync_RoleWithDatabaseName_Cancellation(ApplicationRoles roles, string databaseName)
{
// Arrange
var applicationRoleStore = CreateApplicationRoleStore();
CancellationTokenSource cts = new();
CancellationToken cancellationToken = cts.Token;
_mockApplicationRoleDataManager.Setup(x => x.AddToDatabaseAsync(roles, databaseName))
.Callback<ApplicationRoles, string>((roles, databaseName) =>
{
cts.Cancel();
})
.Returns(Task.CompletedTask);
// Act
await applicationRoleStore.AddToDatabaseAsync(
roles,
databaseName,
cancellationToken);
// Assert
_mockRepository.VerifyAll();
}
}
Attempt 2 (Adding a callback after the return):
[TestClass]
public class ApplicationRoleStoreTests
{
private MockRepository _mockRepository;
private Mock<IApplicationRoleDataManager> _mockApplicationRoleDataManager;
[TestInitialize]
public void TestInitialize()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_mockApplicationRoleDataManager = _mockRepository.Create<IApplicationRoleDataManager>();
}
private ApplicationRoleStore CreateApplicationRoleStore()
{
return new ApplicationRoleStore(
_mockApplicationRoleDataManager.Object);
}
[DynamicData(nameof(GetValidDataDatabasesExistWithDatabaseName), DynamicDataSourceType.Method)]
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task AddToDatabaseAsync_RoleWithDatabaseName_Cancellation(ApplicationRoles roles, string databaseName)
{
// Arrange
var applicationRoleStore = CreateApplicationRoleStore();
CancellationTokenSource cts = new();
CancellationToken cancellationToken = cts.Token;
_mockApplicationRoleDataManager.Setup(x => x.AddToDatabaseAsync(roles, databaseName))
Returns(Task.Run(async () => { await Task.Delay(1000) }))
.Callback<ApplicationRoles, string>((roles, databaseName) =>
{
cts.Cancel();
});
// Act
await applicationRoleStore.AddToDatabaseAsync(
roles,
databaseName,
cancellationToken);
// Assert
_mockRepository.VerifyAll();
}
}
Attempt 3 (Returning a cancel):
[TestClass]
public class ApplicationRoleStoreTests
{
private MockRepository _mockRepository;
private Mock<IApplicationRoleDataManager> _mockApplicationRoleDataManager;
[TestInitialize]
public void TestInitialize()
{
_mockRepository = new MockRepository(MockBehavior.Strict);
_mockApplicationRoleDataManager = _mockRepository.Create<IApplicationRoleDataManager>();
}
private ApplicationRoleStore CreateApplicationRoleStore()
{
return new ApplicationRoleStore(
_mockApplicationRoleDataManager.Object);
}
[DynamicData(nameof(GetValidDataDatabasesExistWithDatabaseName), DynamicDataSourceType.Method)]
[TestMethod]
[ExpectedException(typeof(OperationCanceledException))]
public async Task AddToDatabaseAsync_RoleWithDatabaseName_Cancellation(ApplicationRoles roles, string databaseName)
{
// Arrange
var applicationRoleStore = CreateApplicationRoleStore();
CancellationTokenSource cts = new();
CancellationToken cancellationToken = cts.Token;
_mockApplicationRoleDataManager.Setup(x => x.AddToDatabaseAsync(roles, databaseName))
.Returns(Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
cts.Cancel();
}));
// Act
await applicationRoleStore.AddToDatabaseAsync(
roles,
databaseName,
cancellationToken);
// Assert
_mockRepository.VerifyAll();
}
}
What would be the best way to allow the delay and then cancel the method? Attempts 2 and 3 have been sporadic with the parameters going through, out of the two runs with the input parameters, one might cancel and the other one fail or they will both fail.