5

I have a method as below.

I would like to write two test cases for the below method.

1) A successful transaction with committing data

2) A failed transaction with rollback data

How can I write a test case with transaction involved and make success and fail?

public async Task<List<string>> UpdateRequest(MetaData data, List<string> Ids, string requestedBy)
{
    var transaction = await _databaseUtility.CreateTransaction(ConnectionString);

    var messages = new List<string>();

    try
    {
        // Update data
        await _testDal.Update(data, requestedBy, transaction);

        // Update status
        await _sampleDal.UpdateStatus(Ids, requestedBy, transaction);

        // Update saved data
        await _testDal.UpdateSavedData(data, requestedBy, transaction);

        _databaseUtility.CommitTransaction(transaction);
    }
    catch (Exception exception)
    {
        _databaseUtility.RollbackTransaction(transaction);
    }

    return messages;
}
MikeLimaSierra
  • 799
  • 2
  • 11
  • 29
Chatra
  • 2,989
  • 7
  • 40
  • 73

2 Answers2

4

You can achieve it by using NUnit built-in TestCaseSource attribute and TestCaseData class.

[TestCaseSource(nameof(TestCases))]
public List<string> TestTransaction(MetaData data, List<string> ids, string requestedBy)
{
    return testObject.UpdateRequest(data, ids, requestedBy).GetAwaiter().GetResult();
}

public static IEnumerable TestCases
{
    get
    {
        //successful
        yield return new TestCaseData(new MetaData(), new List<string> { "1", "2" }, "test").Returns(new List<string> { "test1", "test2" });
        //failed
        yield return new TestCaseData(null, new List<string> { "1", "2" }, "test").Returns(new List<string> { "test1", "test2" });
        //another failed
        yield return new TestCaseData(new MetaData(), new List<string> { "1", "2" }, string.Empty).Returns(new List<string> { "test1", "test2" });
    }
}

There a couple of points here

  • Why do you need a List of messages in UpdateRequest method? It's only initialized, values aren't added. In code snippet above the list of messages returned from the UpdateRequest method are simply matched with the expected result, but you can write any Assert here as well
  • Failed transaction depends on your logic, you can pass an invalid data which causes an exception, in code snippet I've used empty Metadata or requestedBy
  • async Task<List<string>> return result is executed synchronously to match the expected return result List<string>. Async test case sources are open proposal in NUnit github

As an option, you can also write a separate tests for successful and failed transactions and call UpdateRequest method asynchronously.

You haven't shared a details of your components so far, and it's hard to make an assumption about DAL, but if you want to use a mocks for Update methods, you can try a Moq library and Task.FromResult method for responses/return values, as it described in this answer

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66
4

I think you have 2 problems here:

  1. you are using async and await in unit testing which means your code will continue to run before anything was really done.
    you can read this https://johnthiriet.com/removing-async-void/#

    i will explain the catch, make sure await _testDal.Update(data, requestedBy, transaction); Update() function returns a Task.
    And then you can call it with _testDal.Update(data, requestedBy, transaction).Wait() for unit testing.
    and the await call for your regular code. this will make your testing code wait until Update finishes, while the regular code stays as is.

  2. I would use a mocking service like NSubstitute https://nsubstitute.github.io/ in order to mock the responses. a mocking service will use reflection to replace the interface or class which implements _testDal.
    this way when you call Update() function you can pass certain paramters to it and throw an exception when those parameters are passed into the function.
    And pass another set of parameters and return the correct messages for them.

if you wish to pass different parameters you can use Nunit TestCase(new[] params...) and pass a different set of parameters to the same unit tests. however for PassTest and FailTest i would use 2 or more different tests.

Gilad
  • 6,437
  • 14
  • 61
  • 119