Search code examples
c#testingmoqhangfire

Verify BackgroundJob.Delete in Hangfire in mstest unit testing


I have a CancelScheduledJob method, taking in the Id of a backgroundjob and using the JobStorage to retrieve this Id and cancelling the matching hangfire job:

var mon = JobStorage.Current.GetMonitoringApi();
var scheduledJobs = mon.ScheduledJobs(0, int.MaxValue);
var jobsToDelete = scheduledJobs.Where(job => job.Value.Job?.Args?.Any(arg => arg is Guid guid && guid == id) == true).ToList();

jobsToDelete?.ForEach(job => _backgroundJobClient.Delete(job.Key));

Verifying on an Enqueue(), or Schedule() method is possible by verifying the Create method called in the backgroundJobClient mock, such as here:

_backgroundJobClientMock.Verify(x => x.Create(
                    It.Is<Job>(job => job.Method.Name == "Run" && Guid.Parse(job.Args[0].ToString()) == input),
                    It.IsAny<ScheduledState>()));

But how would I go about verifying the Delete method? I am already mocking the JobStorage, but cannot seem to find a way to verify the Delete() method. Currently I have this:

_backgroundJobClientMock.Verify(
                x => x.Delete(It.Is<string>(jobId => jobId == "job1")),
                Times.Once
            );

But I run into the common issue that Delete is an extension method and cannot be used in a setup/verification expression.


Solution

  • You can do something "flaky" and test for the instance method that is called from the extension method - (current source code):

    public static bool Delete(
        [NotNull] this IBackgroundJobClient client,
        [NotNull] string jobId,
        [CanBeNull] string fromState) {
        if (client == null) throw new ArgumentNullException(nameof(client));
    
        var state = new DeletedState();
        return client.ChangeState(jobId, state, fromState);
    }
    

    This would be ChangeState, so Verify against it.

    EDIT:

    If you cannot or (don't want to) wrap things in a custom interface in your actual code as the other answers suggest, you can check this answer of mine for an alternative approach. It was for verifying ILoggers extension methods like LogInformation, but it should work here too if you are depending on IBackgroundJobClient. In a nutshell, You create a IBackgroundJobClientExt:IBackgroundJobClient interface that includes the extension methods explicitly. Then use DispatchProxy to create an instance of it and pass it to our sut. That way you can still Verify against Delete in your testing code and maintain the connection between extension methods and instance methods they call in a single place - in the DispatchProxy's Invoke method.