Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Mocking #196

Open
ghost opened this issue Jun 16, 2020 · 5 comments
Open

Add support for Mocking #196

ghost opened this issue Jun 16, 2020 · 5 comments

Comments

@ghost
Copy link

ghost commented Jun 16, 2020

As far as I can tell, all IFirebaseServices are sealed and have internal constructors.

This presents us with the challenge of how we can test code, which uses the FirebaseAdminSdk, which contains very critical operations.
I ask you to re-evaluate this design decision, as mocking is required to achieve a high test coverage of our own code, which interfaces with FirebaseAdmin SDK directly.

In my project, I was able to identify two critical firebase services, which lack mockability:

  • FirebaseAdmin.Messaging.FirebaseMessaging
  • FirebaseAdmin.Auth.FirebaseAuth

My environment:

  • .Net Core 3.1
  • NUnit 3.12.0
  • Moq 4.14.1
@Dongata
Copy link

Dongata commented Jun 16, 2020

If it is of any help, on other context, i work-arrounded this issue with some effy use of reflection, in this case for firebase Auth, im guessing that you can use the same steps for fcm

/// <summary>
/// A really weird way to instanciate what we need to test, 
/// working arround internal classes and missing constructors
/// </summary>
private static FirebaseToken AssambleAToken(string subject, string phoneNumber)
{
    // The thing we want to create
    var fireType = typeof(FirebaseToken);

    // Get Firebase Assambly
    var assembly = fireType.Assembly;

    // Find an internal type that handles the args
    var argType = assembly.GetTypes()
        .FirstOrDefault(a => a.Name == "FirebaseTokenArgs");

    // Create the instance without using any constructor
    var args = FormatterServices.GetUninitializedObject(argType);

    // Get all the properties
    var argProps = argType.GetProperties();

    // Set the subject
    argProps.First(a => a.Name == "Subject").SetValue(args, subject);

    // Set claims
    var claims = new Dictionary<string, object>
    {
        { "phone_number", phoneNumber }
    };
    argProps.First(a => a.Name == "Claims").SetValue(args, claims);

    // Get the appropiate internal ctor (im starting to hate my life)
    var ctors = fireType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);

    // Instanciate the final result with the weirdly formatted args, and return it
    return ctors[0].Invoke(new object[] { args }) as FirebaseToken;
}

@ghost
Copy link
Author

ghost commented Jun 16, 2020

If it is of any help, on other context, i work-arrounded this issue with some effy use of reflection, in this case for firebase Auth, im guessing that you can use the same steps for fcm

/// <summary>
/// A really weird way to instanciate what we need to test, 
/// working arround internal classes and missing constructors
/// </summary>
private static FirebaseToken AssambleAToken(string subject, string phoneNumber)
{
    // The thing we want to create
    var fireType = typeof(FirebaseToken);

    // Get Firebase Assambly
    var assembly = fireType.Assembly;

    // Find an internal type that handles the args
    var argType = assembly.GetTypes()
        .FirstOrDefault(a => a.Name == "FirebaseTokenArgs");

    // Create the instance without using any constructor
    var args = FormatterServices.GetUninitializedObject(argType);

    // Get all the properties
    var argProps = argType.GetProperties();

    // Set the subject
    argProps.First(a => a.Name == "Subject").SetValue(args, subject);

    // Set claims
    var claims = new Dictionary<string, object>
    {
        { "phone_number", phoneNumber }
    };
    argProps.First(a => a.Name == "Claims").SetValue(args, claims);

    // Get the appropiate internal ctor (im starting to hate my life)
    var ctors = fireType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);

    // Instanciate the final result with the weirdly formatted args, and return it
    return ctors[0].Invoke(new object[] { args }) as FirebaseToken;
}

Thanks, this helps me create my response objects (I didnt know FormatterServices.GetUninitializedObject), but mocking the services methods in a sane way is still missing some pieces (Not too excited to write my own mocking framework for this SDK)

@floppydisken
Copy link
Contributor

floppydisken commented Mar 16, 2021

I ended up wrapping the functionality I wanted in an IFirebaseService interface with the features I needed from firebase. I've then wrapped an implementation around the FirebaseAdmin SDK

public interface IFirebaseService
{
    Task<User> GetUserAsync(string uid);
    Task UpdateUserAsync(UpdateUserArgs args);
    Task SetRoleAsync(string uid, UserRole role);
    Task<string> GenerateEmailVerificationLinkAsync(string email);
}

@nmehlei
Copy link

nmehlei commented May 26, 2021

@Dongata Thank you for the helpful solution!
One note for future readers: It looks like the FirebaseTokenArgs class was renamed to just Args, as can be seen here: https://github.com/firebase/firebase-admin-dotnet/blob/master/FirebaseAdmin/FirebaseAdmin/Auth/FirebaseToken.cs#L83

@Dongata
Copy link

Dongata commented May 26, 2021

@nmehlei You welcome man, I had to update the test again (we updated everything to net 5) here's the code updated, hope it helps.

/// <summary>
/// A really weird way to instanciate what we need to test, 
/// working arround internal classes and missing constructors
/// If you're brave enough try to understand it, i mean, it's not as bad
/// it's full of comments.
/// </summary>
private static FirebaseToken AssambleAToken(string subject, string phoneNumber)
{
    // The thing we want to create
    var fireType = typeof(FirebaseToken);

    // Get firebaseToken internal args
    var internalTypes = fireType.GetNestedTypes(BindingFlags.NonPublic);

    // Find an internal type that handles the args
    var argType = internalTypes
        .FirstOrDefault(a => a.Name == "Args");

    // Create the instance without using any constructor
    var args = FormatterServices.GetUninitializedObject(argType);

    // Get all the properties
    var argProps = argType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);

    // Set the subject
    argProps.First(a => a.Name == "Subject").SetValue(args, subject);

    // Set claims
    var claims = new Dictionary<string, object>
    {
        { "phone_number", phoneNumber }
    };
    argProps.First(a => a.Name == "Claims").SetValue(args, claims);

    // Get the appropiate internal ctor (im starting to hate my life)
    var ctors = fireType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);

    // Instanciate the final result with the weirdly formatted args, and return it
    return ctors[0].Invoke(new object[] { args }) as FirebaseToken;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants