C# - Unit test method that raises event with Guid argument

Viewed 336

I'm trying to figure out how to test this scenario and coming up blank.

I have a listener service that will trigger the following when a file is detected in a mailslot:

IEventDispatcher _dispatcher;
IListener _mailslotListener;

public Service (IEventDispatcher dispatcher, IMailslotListener mailslotListener) {
  _dispatcher = dispatcher;
  _mailslotListener = mailslotListener;
}

public async void _Listener_OnFileDetected(object sender, MailslotArgs) {
  string filePath = args.Message;

  Directory.CreateDirectory(TempLocation);

  FileId = Guid.NewGuid();
  string tempPath = Path.Combine(TempLocation, FileId.ToString());
    
  File.Move(filePath, tempPath);

  await _dispatcher.DispatchEvent("documentPrinted", FileId);
}

This will move the file passed in to a temp location and rename it with a Guid. In my test, I'm trying to verify that the file was moved successfully, but I'm not sure how to access the Guid used and passed to the _dispatcher.DispatchEvent call.

I'm able to verify that the event was raised, but that's as far as I've gotten as the value being a random Guid seems to be an issue when asserting equality in the test.

[Test]
public void OnFileDetected_CallsDispatchEvent()
{
  Directory.CreateDirectory(tempPath);
  listenerService._Listener_OnFileDetected(null, new MailslotArgs(testFilePath));

  mockEventDispatcher.Verify(m => m.DispatchEvent("fileDetected", 
                                  It.IsAny<Guid>()), 
                                  Times.Once);
}
1 Answers

This is a typical issue with static initializers. It's well known as the DateTime.Now variant, but the outcome is the same.

The problem is that, due to it's static nature, it's hard to mock the output of the method.

Since it's used within the implementation the outcome of the GUID is kept within, and there is no easy way to access it.

A common approach is to create a wrapper class + interface, to expose the Guid.NewGuid() method.

Using this approach you have full control over the Guid generation - you just need to use the appropriate class.

//The interface
public interface IGuidGenerator
{
     Guid NewGuid();
}

//Use in the application
public class GuidGenerator: IGuidGenerator
{
    public Guid NewGuid()
    {
        return Guid.NewGuid();
    }
}

//Use in the test scenario
public class TestGuidGenerator: IGuidGenerator
{
    public Guid NewGuid()
    {
        return new Guid("dddddddd-dddd-dddd-dddd-dddddddddddd");
    }
}

If you are using an IoC framework, it would just be a matter of registering the interface and class, and pass the generator to the service:

public Service (IEventDispatcher dispatcher, 
                IMailslotListener mailslotListener, 
                IGuidGenerator guidGenerator) 
{
   _dispatcher = dispatcher;
   _mailslotListener = mailslotListener;
   _guidGenerator = guidGenerator;
}

The DateTime.Now variant, is actually commonly used as an interview question. Here is some more on the subject: https://dvoituron.com/2020/01/22/UnitTest-DateTime/

Related