Best way to unit test cancellation

Michael Mastro II 56 Reputation points
2022-07-22T02:01:05.437+00:00

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.

Developer technologies | Visual Studio | Testing
Developer technologies | C#
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.