Search code examples
c#unit-testingasynchronous.net-4.0nunit-3.0

How to unit-test the progress report of an async call?


Goal

I want to test the progress reporting of some async call which accepts a IProgress. I want to verify:

  • if this reported progress is always within the range of [0.0; 1.0]
  • has increasing values (last value <= next value)
  • reports 1.0 at some point, eg as last call

I want to use as few frameworks as possible -> a solution without using another 3. party library would be nice.

Problem/My trys

I tryed to write a simple async Task test methode to the achive this. I call the methode to test with await eg. await asyncCallToTest(new Progress((p) => { Assert.someting(p); })). And maybe use stack variables in the lambda. Full examples later.

This however resulted in Tests that succseed even if it should not be possible or sometimes a test passed and then it failed without changes....

Code

I have recreated the issues with a simple stand alone test class:

using System;
using System.Threading.Tasks;

using NUnit.Framework;

namespace FastProgressReporter
{
    public class Tests
    {
        [Test]
        public async Task SampleOkTest()
        {
            await TestReporter(new Progress<double>((p) => {
                Assert.GreaterOrEqual(p, 0);
                Assert.LessOrEqual(p, 1);
            }));
            //Sometimes fails, sometims passes. Why?
        }

        [Test]
        public async Task SampleFailTest()
        {
            await TestReporter(new Progress<double>((p) => {
                Assert.GreaterOrEqual(p, 2); // Has to Fail, however passes.
                Assert.LessOrEqual(p, 3);
            }));
        }

        [Test]
        public async Task ReportsIncremental()
        {
            double latestProgress = 0;
            await TestReporter(new Progress<double>((p) => {
                p = 1 - p; //Force Decrement...
                Assert.GreaterOrEqual(latestProgress, p); //Should fail on second call
                latestProgress = p;
            }));
        }

        [Test]
        public async Task ReportsOneOneOrMoreTimes()
        {
            double maxProgress = 0;
            await TestReporter(new Progress<double>((p) => {
                maxProgress = p > maxProgress ? p : maxProgress;
            }));
            Assert.AreEqual(0, maxProgress); //Should always fail
            //Sometimes fails, sometims passes. Why?
        }

        // Methode under test
        public static async Task TestReporter(IProgress<double> progress)
        {
            progress.Report(0);
            await Task.Run(() => { /*Do nothing, however await*/ });
            progress.Report(0.5);
            await Task.Run(() => { /*Do nothing, however await*/ });
            progress.Report(1);
        }
    }
}

Envoriment

VisualStudio 2019, .net-framework 4.7.2, NUnit3 test project

Quesiton

How do I achive my goals reliable and correctly?

Releated Questions

Verifying progress reported from an async call is properly report in unit tests This uses a 3 party libary which I am trying to avoid and has the .Report() call inside of the test methode directly, not in the async mehoed called by the test

Thank you for your help.


Solution

  • Soluion

    As suggested by @Andrei15193, I tryed to implement IProgress by my self to get rid of the Progress class.

    private class UnitTestProgress<T> : IProgress<T>
    {
        private Action<T> handler;
    
        public UnitTestProgress(Action<T> handler)
        {
            this.hander = handler?? throw new ArgumentNullException(nameof(hander));
        }
    
        public void Report(T value)
        {
            handler(value);
        }
    }
    

    I have chosen a very simple class here as an implmentation.

    As far as I have tested, if Progress is replaced by my new class the problems are solved and the unittests are reliable.