REST API Tests

API Testing - White text on a blue background with multiple software based terms related to testing

If you review the tests for the Costs to Expect API, you will notice two things; I have a heavy preference for functional tests and I don’t tend to mock anything.

API tests and Mocking?

I don’t mock any services; I want my test environment to match my intended live environment as closely as possible. This means using all the services used by live, I want to know of integration issues prior to going live, all my API tests will run as though they were running on the live environment.

Additionally, my tests write data. They write data using the same system as live. If live writes to a MySQL database, so do all my API tests, I have never seen the point of using a different engine for testing, you are not testing like for like.

Functional tests

Most of my API tests are functional tests, they test a process. Unit tests are incredibly useful, but most of your tests should be functional or integration tests.

My goal with API testing is making sure everything works as expected for the user and any clients. Testing all the scenarios a user or client is likely to see, this means all the possible “400 errors”.

The unhappy path

We need to test the happy path but we should focus most of our effort on the unhappy path, this is where the awkward bugs lie and the bugs we didn’t find during development. In addition to testing the happy path, I include tests to validate the structure of data. I’m a massive fan of self-documenting APIs and include response tests which validate that the generated OPTIONS response matches what I expect.

My API action tests will include the expected, create, read, patch, and delete tests. They are not my priority, I create many more tests to ensure the correct validation messages are returned, ensuring parameters work as expected, testing where a user or client is likely to get stuck.

If there is a validation rule against a field, group of fields or some other explicit logic (PATCH body can’t be empty), I add a test to ensure it works as expected.

Postman collection vs local API tests

Up until recently, the API tests for the Costs to Expect API existed as a Postman collection. The collection worked until it didn’t. It is still a fantastic API monitor but as the scope of the API grew, it became painstakingly difficult to ensure the code and tests were inline, authoring tests is also tedious and slow with Postman.

I have/am moving everything local and testing with PHPUnit, I couldn’t be happier, tests can be tied to releases, I can run them without cost and although I’m still functionally testing, I’m not limited to setting some fixed input and testing only the generated output.

OAuth2 and Business Central

Authentication written in block text

Background

I started developing a B2B App for a client which initially needed to integrate with Sage. However, midway through development the client decided to move away from Sage and use Business Central (Dynamics 365), that means OAuth2.

The integration requirements increased significantly; we went from a system needing to occasionally pass some information to Sage to a system which was heavily integrated with BC (Business Central) and required an almost constant connection.

In this post we will ignore the feature creep and other issue and just discuss the integration.

App registration in Business Central

Like most developers, I understood OAuth2, or at least thought I understood it. I’d worked on Apps using OAuth2 and had had to make changes and tweaks, but I’d never had to roll my own client and more importantly connect it to another service. That ended up being the biggest problem.

If you need to integrate with Facebook, GitHub or any of the other large providers, there will be a well-supported package, the league has you sorted for most things. In my case I was connecting to a customer’s instance of BC.

I spent a long time reading all the Business Central documentation and finally came across this, I needed to create an App registration in Azure. Once we had created a valid registration and confirmed it was working (thanks Postman) I needed to get my App to talk to BC, for that I needed an OAuth2 client.

OAuth2 client

I didn’t find a client or integration anywhere that suited my purposes, so I decided to roll my own using the OAuth2 generic provider by the league – yes, they don’t recommend this.

I created a wrapper class with four public methods, hasToken(), token(), refresh() and authorize().

hasToken()

This method does as the name suggests, it lets us know if we have a valid token.

token()

This method returns the token, however, before trying to return the token it calls an internal method called fetchOrRefresh(). This method checks to see if we have a token and whether it is valid. If it has expired, or due to expire, we wait and create a new token using the refresh token. There are several long running tasks, so we decided it was best to ensure we know the life of the token before starting a long running task.

refresh() and authorize()

These are wrapper functions which contain all the code necessary to authorize the client and save a token or generate a new token using the refresh token,

Usage within the App

So far, the integration with BC has been almost flawless. We keep an eye on request limits and catch all API errors. To date, we have never had a problem with the OAuth2 system, every hour a new token is created using the refresh token and it all works wonderfully.

So, how does this work in the code?

If a class needs to connect to Business Central, the first thing we do is check to see if we have an active token.

$oauth = new OAuth();
if ($oauth->hasToken() === false) {
    // Handle error state,
    // Send notification
    // Exit task and retry again
}

Now, assuming we have a token which should always be the case after the initial authorisation, all we need to do it fetch the token and pass it along with our request.

$token = $oauth->token();

if ($token === null) {
    // Handle error state
    // Unable to fetch the token or 
    // Unable to generate a new token using the refresh token
    // Exit task and send notification
}

A failure at this point is unlikely and typically will only happen if a service is down. As previously mentioned, the token() method fetches the token and if necessary, ensures a new token is created, other than an outage it is unlikely this code will fail.

The code above is just a simplified example, in the real app we have much more error checking and state management and throttling. The client can retry any retriable request and see a full log of all interactions between our App and BC.

Conclusion

Was any of this fun?

No. When under serious time constraints working all of this out was a little stressful, especially when I couldn’t find any examples online

Was it worth it?

Yes. My understanding of OAuth2 and integrating with Azure increased no end, I went from thinking I understood OAuth2 to knowing I understood OAuth2.

This is the App that keeps on giving. You might be interested in my next blog post in the series, UI and UX is tough for B2B Apps.