The problem

I'm using the Firebase Admin dotnet sdk to send push notifications. They have the following method that allows you to send multiple notifications with one request:

public async Task<BatchResponse> SendAllAsync(IEnumerable<Message> messages)
{
    return await this.SendAllAsync(messages, false).ConfigureAwait(false);
}

The SendAllAsync method returns a Batchresponse that will tell you how many notifications that were sent successfully and how many that failed. Usually when a notification fails to deliver it's because of the token used has expired or been revoked.
Whenever I receive a response that tells me that the token is invalid I want to remove that token from the database so I don't end up with a bunch of expired/invalid push tokens.

The logic is quite simple

var response = await _firebaseMessaging.SendAllAsync(messages);
return HandleResponse(response);

I then have the following code that acts on the response:

// This is MY code that takes a BatchResponse (from the Google library).
public async Task<Result> HandleResponse(Batchresponse response)
{
    if (response.SuccessCount > 0 && response.FailureCount == 0)
    {
        return Result.Ok();
    }
    
    if (response.FailureCount > 0)
    {
       var invalidTokens = ExtractInvalidTokens(response);
       await _removeTokensCommand(invalidTokens);
       return Result.Fail();
    }
    ...
}

You get the idea, if we have any failed tokens, remove them.
So, seems quite easy to test this, right?

Well, turns out that BatchResponse looks like this:

public sealed class BatchResponse
{
    internal BatchResponse(IEnumerable<SendResponse> responses)
    {
        // some irrelevant code here
    }
    
    public IReadOnlyList<SendResponse> Responses { get; }
    public int SuccessCount { get; }
    public int FailureCount => this.Responses.Count - this.SuccessCount;
}

It's sealed and the constructor is internal, meaning we can not create a new instance of it like this:

var batchResponse = new BatchResponse(new List<SendResponse>());

What to do?

The solution

Reflection to the rescue.

By using reflection it's possible to do...basically whatever you want, you can set private fields, you can call methods that are not public and so on.

It goes without saying but you should probably not rely on what I'm about to show you in your production code. Classes are (almost) always sealed for a good reason, same goes with internal constructors, the developers clearly don't want you to create instances of their classes (for whatever reason).

One simple solution in my case would be to change the signature of my HandleResponse method to the following...

Task<Result> HandleResponse(int successCount, int failureCount)

...or just map it to some domain object that I control.

But, HOW FUN IS THAT?

This is what I came up with:

public async Task GivenOneSuccessAndZeroFailures_WhenHandleResponse_ThenReturnsOkResult()
{
    // Create the instances
    var sendResponse = CreateInstance<SendResponse>(
        BindingFlags.NonPublic | BindingFlags.Instance,
        new[] { typeof(string) },
        new object[] {"foo"});
    var batchResponse = CreateInstance<BatchResponse>(
        BindingFlags.NonPublic | BindingFlags.Instance,
        new[] { typeof(IEnumerable<SendResponse>) },
        new object[] { new List<SendResponse>{ sendResponse }});
        
    // Pass the mocks to our code
    var result = await sut.HandleResponse(batchResponse);
    
    result.Success.ShouldBeTrue();
}

private static T CreateInstance<T>(BindingFlags bindingFlags, Type[] types, object[] ctorParams)
{
    var type = typeof(T);
    var constructor = type.GetConstructor(
        bindingFlags,
        null,
        types,
        Array.Empty<ParameterModifier>()) ?? throw new Exception("This didn't work...");
        
    return (T)constructor.Invoke(ctorParams);
}
  1. Get the relevant constructor
  2. Invoke the constructor
  3. Cast the result to T
  4. BOOM, we've just created an instance of a sealed class without any public constructors.