-
Notifications
You must be signed in to change notification settings - Fork 133
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
Support for dependency injection with interfaces for testing purposes #139
Comments
@Dongata can you elaborate what exactly you mean by services? In general, this is the pattern I'd recommend for developers (taking
Then you use the |
Yeah, we're using it like that, But i think that will be great if that the inversion of control, can be supported by default on the library, given that is like a netcore standard. Thanks for your help tho. |
Most of the time defining such interfaces in the SDK is counterproductive. Each user only cares about a subset of the SDK methods, which means one of 2 things can happen:
Personally I think asking developers to define their own interfaces is the best course of action. Since developers know best about their specific application requirements, they are in the best position to decide what these interfaces should look like. I will keep this issue open for a bit longer to see if it receives any additional feedback/suggestions. |
@hiranya911 The solution you propose merely shifts the problem: Please re-evaluate this design decision, as it introduced great risk at critical services, interfacing with FirebaseAuth and FirebaseMessaging, for example. Mocking can be supported by offering non-sealed classes using Moq. And please rename this issue, its name is very misleading: |
Yeah you right about the title, english is not my first language c: Mocking those services is not imposible, is just annoying. i'll pass you an example on your issue, hope it helps |
Assuming that class simply turns around and calls the Mocking is just a way to swap out third-party or otherwise unmodifiable code with your own code for testing. The solution above has the same net result. You just treat
If we were to support direct mocking of SDK interfaces, I'd like to think through different options and the developer experience a bit more before we start unsealing our APIs. There are other classes like |
Also related to #158 |
By introducing interfaces, the developer receives more flexibility and a cleaner public interface. Unsealing classes simply enables developers to extend your classes and add extra code they require (and also add Mocks, if you have no interfaces). Of course mocking of services would also require the possibility to create new BatchResponses using a public constructor. As this is just a data object, I see no reason for an internal constructor. Simplified example of mocking and testing a service, which uses a firebases service (e.g. verifying that we treat the response objects correctly) //TODO impossible to Mock with FirebaseAdmin 1.13.0 (sealed class without interface)
var firebaseMessagingMock = new Mock<FirebaseMessaging>();
firebaseMessagingMock
.Setup(messaging => messaging.SendMulticastAsync(It.IsAny<MulticastMessage>()))
.ReturnsAsync(() =>
new BatchResponse(new[]
{
SendResponse.FromMessageId("messageId1"),
SendResponse.FromMessageId("messageId2")
}));
var firebaseService = new FirebaseService(otherServiceMock.Object, firebaseMessagingMock.Object, Bouncer.Instance,
NullLogger<FirebaseService>.Instance);
// TODO test firebaseServiceBehaviour |
Couple of points worth noting here:
I'm not against making it possible to mock certain APIs. But if we do that I'd like to do it in a SDK-wide, consistent way (or at least have a plan to make it so). Focusing on 1-2 classes at a time is likely not going to work out well in the long run.
What is It also seems this is simple enough to handle via some wrappers in your code (at least as a stop gap measure).
Now you can mock |
I ran into the same issue trying to mock BatchResponse in my unit tests. I found the same issue submitted on firebase-admin-java repo This is the PR that soloved the issue on java repo. This would make writing unit tests around the api much simpler for our c# projects. |
If someone has this same issue, here's a work around |
Bumping this as my team is also running into this issue. This all seems to be a matter of opinion with one camp saying, "You should always use a pure adapter" and the other saying, "Make an Pure AdapterIf you are using a pure adapter, you're creating a public class MyMessage
{
// Wrapper around FirebaseAdmin.Messaging.Message
public Message ToMessage()
{
// Conversion code for FirebaseAdmin.Messaging.Message
}
}
public interface IMyFirebaseService
{
public Task<string> SendMessageAsync(MyMessage message);
}
public class MyFirebaseService : IMyFirebaseService
{
public async Task<string> SendMessageAsync(MyMessage message)
{
var firebaseApp = FirebaseApp.GetInstance("Firebase");
var firebaseMessaging = FirebaseMessaging.GetMessaging(firebaseApp);
return await firebaseMessaging.SendAsync(message.ToMessage());
}
} And using it like: public class MyNotificationService
{
private readonly IMyFirebaseService _myFirebaseService;
public MyNotificationService(IMyFirebaseService myFirebaseService)
{
_myFirebaseService = myFirebaseService;
}
public async Task SendNotificationAsync(MyMessage message)
{
var result = await _myFirebaseService.SendMessageAsync(message);
// Do something based on result
}
} In this case, you treat Dependency Injection AdapterWith a Dependency Injection adapter, public interface IMyNotificationService
{
public Task<string> SendMessageAsync(Message message);
}
public class MyNotificationService : IMyNotificationService
{
private readonly IFirebaseApp _firebaseApp;
private readonly IFirebaseMessaging _firebaseMessaging;
public MyNotificationService(IFirebaseApp firebaseApp)
{
_firebaseApp = firebaseApp;
_firebaseMessaging = _firebaseApp.GetMessaging();
}
public async Task SendMessageAsync(string title)
{
var message = new Message
{
// Implementation details
Title = title
};
var result = await _firebaseMessaging.SendAsync(message);
// Do something with result
}
} In this case, The downside here is the need to mock The other downside is the OpinionThe point of pure adapters is to hide implementation details from callers of the adapter, require loose coupling, and make swapping components easier because the business logic is separated from implementation details. However, pure adapters also require creating (at least some) boilerplate code and (potentially) extra classes/interfaces to wrap the adapted code, its exceptions, and its data structures. When the adapted code changes either from moving to the next major version or changing the underlying provider, the adapter's code must also change. Those changes typically cascade up the call stack when our abstractions must change. A point that is hammered into our heads when learning Object-Oriented Programming and reading books like Clean Code is the need for flexibility and loose coupling in our designs. In fact, that is why the adapter pattern exists. The quest for this ideal often leads to over-engineered and complex code with hidden technical debt where our adapter abstractions still end up being too tightly coupled and the need to change the providers used by our adapters does not happen frequently enough to justify the extra work of creating the adapter in the first place. Instead, the adapter acts as a warm blanket so we can sleep easier at night knowing that if we had to change providers, we could. But we probably won't, or, at least, won't change if often enough that the effort to create a pure adapter pays off. Implementing interfaces for the public functionality in the Firebase Admin SDK still allows users who want to create a pure adapter to continue to do so. It also allows users who want to leverage Dependency Injection and Inversion of Control to do so without needing to resort to Fakes or Shims. |
+1 here. Same problems - SDK is untestable out of the box. 👎🏿 |
+1 |
The amount of wrappers needed to crate a BatchResponse that can be mocked is still unreasonable. At least the response composition should be done with interfaces. So that when you create an SDK wrapper you can mock the responses. And your wrapper contains no logic beside proxying. |
Can you please extract the signatures of the admin classes into services so we can mock them to test our code
Thank you
The text was updated successfully, but these errors were encountered: