stripe-java
stripe-java copied to clipboard
Support mocking of stripe outbound RPCs for unit tests
Is your feature request related to a problem? Please describe.
When writing unit tests, there is a need to avoid outbound RPCs to another server. Today the Stripe API uses static methods and global (or request) config parameters, but there does not seem to be a way to mock the request/verify the request without running StripeMock, which is perhaps too large for a java unit test.
Describe the solution you'd like
If there was a constructor/factory based approach to constructing the stripe api, then we would be able to mock this away in our DI layer.
Today
Customer.create(customerCreateParamsObj);
Desirable alternatives
stripeFactoryObj.customerService().create(customerCreateParamsObj);
In my code I would likely provide CustomerService via DI, and then call like such:
customerService.create(customerCreateParamsObj);
This way I would be able to provide a Mockito implementation for CustomerService and setup mocks/verifications.
Describe alternatives you've considered
My solution until this is supported will be to wrap every Stripe API in a class/method and indirectly call the stripe API.
Alternatively it seems clients could use Mockito to mock static methods, though this approach is less desirable.
Additional context
No response
Hello @Kurru,
I totally agree that the static implementation of the stripe API is for unit testing a pain and addition to that not the java way. I was surprised, that the stripe-node implementation, with out looking to deep to the code, creates objects for it (see https://github.com/stripe/stripe-node#usage-with-typescript).
But nevertheless, here is an example how you can mock with mockito the stripe API:
@Test
void createCustomer() {
try (MockedStatic<Customer> customerMockStatic = Mockito.mockStatic(Customer.class)) {
Customer customerMock = mock(Customer.class);
customerMockStatic.when(() -> Customer.create(Map.of("email", "email", "metadata", Map.of("key1", "value1")))).thenReturn(customerMock);
Customer customer = stripeBillingRepository.createCustomer("email", Map.of("key1", "value1"));
assertEquals(customerMock, customer);
}
}
There is also another alternative (but not tested from me): https://github.com/stripe/stripe-java/issues/93#issuecomment-349833455
Hello @Kurru, thank you for filing this.
You are right, this is a shortcoming of the library. We call your constructor/factory-based approach a "services-based architecture" and it is something we do intend to add to stripe-java, and are actively designing. We did something similar for stripe-php
and stripe-java will likely be next.
I'll leave this open, mark as "future" and we'll be sure to post updates about our progress here.
If you have any advice about the design, or examples of other libraries with this architecture that you think are designed well, we appreciate any advice or input!
Some typed client libraries I've used recently:
- AWS java library
- Alpaca java library
- gRPC java client library. Can mutate the request by chaining method calls on the rpc stubs: timeouts, retry policy, add RPC interceptors
Some other features you may want to consider
-
Builder
s for domain classes (likeCustomer
) to enable types in tests more easily (instead of long chains of.setXYZ()
) - RPC interceptors to enable global concerns such as monitoring/tracing/logging integrations. Allow users to instrument outbound RPCs to Stripe for monitoring and debug purposes. Alerts, dashboards, AWS X-Ray etc. See GRPC and AWS client libraries for examples
- Support async RPCs in addition to blocking interfaces (like gRPC and AWS API's support). 2nd set of APIs return
CompletableFuture<YourResponseObject>
Additionally, the request objects don't have equality defined, which makes testing harder
New discoveries, timestamps exist, but don't document they are second granularity and are simple Long's. Returning java.time.Instant would be preferred.
Consider adding types for status's. For example, Invoice.status is a string, but really an enum. Consider providing a typed interface for this field.