Open ghost opened 4 years ago
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;
}
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)
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);
}
@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
@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;
}
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:
My environment: