Source: https://www.seguetech.com/how-much-test-coverage-enough-testing-strategy/

Writing Testable Applications with .NET and Stripe

George Kamtziridis
fromScratch Studio
Published in
4 min readNov 22, 2022

--

Nowadays, every modern web application has some kind of payment system integrated. From small businesses that need basic e-commerce platforms to big enterprise companies that handle large amounts of digital money. Implementing payment features can be done in various different ways, where some are ready-to-use solutions and others are more configurable in terms of payment scenarios. When building such infrastructure one must be sure that everything works as intended, since even small mistakes can have disastrous consequences when it comes to billing. To build system reliability, the very first step one must make is to write unit tests. Unit tests make sure that each unit in an application works properly. Usually, by ‘unit’ we mean functions which take an input and return an output.

At fromScratch Studio, we’ve done a lot of research around the topic of online payments and given the fact that we mainly write .NET code, we’ve concluded that Stripe is the best fit for us on many projects. Stripe is a well-known tool providing the building blocks needed to create any possible online payment flow. They support APIs for multiple languages and frameworks, including C# and .NET, with great documentation and basic examples.

Although their tutorials explain really well the .NET API, in most cases the given code is not testable. Again, Stripe demonstrates basic functionalities so one can say their purpose is to just introduce developers to their library, which is totally fine. However, by taking a look at the coding samples online, on Stack Overflow or on other platforms, most developers use Stripe’s examples as their codebase which in fact leads to untestable code.

As we’ve previously mentioned, untestable payment flows can end up in irreversible situations. That’s why here at fromScratch Studio we want to show how one can write testable code with some simple trick. For the purposes of this article, we will go through the 2 fundamental examples showcased below. As for the testing framework, we have used the xUnit library, since it’s one of the proposed ones by Microsoft.

Payment Intents

A “payment intent” is one of the rudimentary blocks, allowing the implementation of custom payment flows. As the official documentation states “It tracks a payment from creation through checkout, and triggers additional authentication steps when required”. When a customer completes the checkout process, we have to create a payment intent by passing in some options. To do that we will use the documentation example:

And the question is, why isn’t this testable? Well, we instantiate the `PaymentIntentService` inside the `Pay` method. By doing so, it is impossible to mock it during testing. We must be able to mock it in order to test different scenarios and make sure the `Pay` method can be deployed to production safely.

How can we resolve this? The answer is straightforward and is known as DI or Dependency Injection. In other words, we need to utilize .NET dependency injection to inject the `PaymentIntentService` into our `PaymentService`. This will allow us later on to fully mock at any extent any method of the injected service.

After injecting the service, don’t forget to register it in the `builder.Services` in the `Program.cs` file.

So, now in the corresponding testing file we can do the following:

Pay attention to line 29, where we have specified what we want from the `PaymentIntentService.Create` to return. With this codebase we can test any scenario we want, depending on the actual payment flow.

Webhooks

A webhook is another crucial component in the overall payment process. Essentially, Stripe allows us to receive asynchronous calls for different events. The most obvious case is the simple checkout, where a customer wants to buy a product by paying the corresponding price. The transaction is considered to be pending until the payment is fulfilled. However, we don’t know when exactly this is going to happen. It may take some milliseconds or a couple of minutes. It may even fail because of high network congestion.

This is where webhooks come into play. We set a webhook and register it to our Stripe account. Then, we wait for events, where based on the type we will act accordingly. To continue the previous example, after creating a payment intent, we need a webhook that will listen to certain events strictly related to payment intents. This webhook usually looks like the next one:

Is this controller testable? As you’ve probably guessed it, no it’s not. We can’t fully test it due to line 20 where we use the `EventUtility.ConstructEvent` directly. Additionally, note the missing `new` keyword. The `EventUtility` is a static class, which means we can’t use DI as we previously did. This scenario requires a different approach. We need to wrap the `EventUtility.ConstructEvent` static method in a new method, let’s say `PrepareStripeEvent`. This will contain only the static method and since it’s an external library, we won’t have to test it at all.

If we incorporate the new `StripeHelperService` into our Controller, we will end up with a fully testable method, which can be mocked just like in the previous example.

Again, don’t forget to add the `StripeHelperService` to the `builder.Services` registry.

Conclusion

Writing testable code might not be trivial, but it’s surely significant. Especially, on high risk cases such as payments and billing, the corresponding lines of code must be of high coverage. Stripe provides a very powerful API for online payments which can be used to create fully testable and reliable applications. It is important to ask ourselves, if our day-to-day code is testable and if it’s not, how can we adjust it to create some room for tests. After reading this article, we hope you will ask this question more and more frequently.

--

--

George Kamtziridis
fromScratch Studio

Full Stack Software Engineer and Data Scientist at fromScratch Studio. BEng, MEng (Electrical Engineering/Computer Engineering) MSc (Artificial Intelligence)