Best way to unit test cancellation

Michael Mastro II 51 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.

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,008 questions
Visual Studio Testing
Visual Studio Testing
Visual Studio: A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.Testing: The act or process of applying tests as a means of analysis or diagnosis.
351 questions
{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.