API Testing
This is me asking you, please stop using Postman, just use some Unit Test Runner with some Restful HTTP library, i.e in C# there is RestSharp.
As with all other chapters let me bore you for about 5 minutes on why I am asking this and what you can do about it.
When I say API in this chapter I am really talking about HTTP based restful web apis, where you make a http call to a specific route and expect a specific response code/data.
What is API Testing?
This level of testing may be called many things, API Testing, Behaviour Testing, Acceptance Testing etc, really all we want to do is verify that when we call the API with a specific state we get the expected outcome, we often align and drive these tests from the acceptance criteria of incoming tasks/stories. If your AC is bad then your tests will probably be sketchy, but in a way this forces you up front to have good tests and not rely upon some test entity somehow making up the AC on the fly when your code is done.
As mentioned in previous chapters Unit Testing
specifically tests that a method does what we expect, it is purely focused on code and may not align with any higher level requirements directly. So this does not replace that sort of testing, but acts at a far higher level.
For example you may have an API that lets you create a user, so your code flow would probably be something like:
Depending on what happens on that journey you may end up returning a 200, 404, 401, 403 etc. You would probably write unit tests for your UserService.CreateUser
as thats doing the heavy lifting here, maybe some tests for a UserValidator
or something if you have validation, the rest would just be optional for if we have time.
However if we just rely on unit tests we can only verify the validator and service layer bits work, we havent checked that we get the right HTTP responses back or checked the DB ends up in the right state, and we cant easily do that via unit testing.
Someone will jump in and say you can mock controllers and db stuff and while you can it often ends up being more faff than its worth, even if you make
integration
or lower levelsystem
tests that focus on the code flow. However here I want to propose we focus on the higher level and instead of testing our code, we test our requirements.
This is where we come to Postman
which is a tool a LOT of places use to solve this problem, there are others, and some places just dont have tests at this level, all of which have their own problems, and hopefully I will address some of them now.
Why is Postman bad?
I dont want to pick on Postman specifically, and its actually a very good tool for what it does, but my main stance here is that you don't need it, and any time I join a team which uses it, we end up with major problems when it comes to testing the APIs. Some problems are more in depth than others but I will try to break down the high level problems (not all may apply to you) then go into possible solutions.
Postman can only interact with the API
This may not seem like a problem at face value, like lets say I want to test that creating a user that already exists returns a 409 (or similar). How do we make sure the user already exists? we could manually fudge the data and tell everyone in the team DONT TOUCH THIS USER, or we could call the user endpoint once with a user that doesnt exist and get that to create it. However then if we do that, what if that call doesnt work? or what if we already added that user from a previous test run?
I am only touching the surface level issues where, but they go deeper, as often most test suites will have daisy chained tests that all NEED to be run SEQUENTIALLY and if any fails the data in the DB will be in an inconsistent state for the tests requiring manual interactions.
This reliance on the API as the only touch point means that you may be subject to side effects too which you may not want or could impact further tests down the line, for example what if your Create User call raises an event for processing async in another app, you creating test data via this will start triggering other processes that are nothing to do with your test case.
Postman needs bespoke test code to be of any use
Almost all postman tests will need some JS code internally to actually do the verification and assertions, maybe even some basic 3rd party calls as part of your test, but all of this test is sat inside of postman and needs their tools to access, its harder to debug and refactor or re-use across other tests. It also cant make any use of your existing rich data/service layer which already exists in your repo, as it has no way to call or consume that code.
These tests are separate to your repo/change management
You are at the mercy of their own branching mechanism and PR process, their tests and logic do not live within your repo with the rest of the codebase, they cant be easily ran from within your repository (will touch on Newman later). They also have a complex environmental management system that means you will often need to ensure you have a lot of config data duplicated between your repo and postman.
Who OWNS the Postman tests
Quite often the testers own the tests in postman, and in some cases will be the sole runners of the tests, this means that the developers never really interact with these tests in any meaningful way, therefore never write any automation for their API changes, its just left to the testers to do and this means lots of code gets pushed up for release before it may even work properly (you would have to manually test it yourself first).
You may work in a place where the testers expect the devs to have some input on the tests and possibly run them, and thats somewhat better as at least you can run them before you push your code up, but as mentioned in the previous example you need to have data setup a specific way or have to have some hacked environment setup to run locally. An even worse scenario is where devs are expected to create and maintain postman tests but the testers run them and complain to devs when things dont work. I worked in a place like this and it was infuriating as it was just such a crippled process where we had to do what would be simple in code in a 3rd party tool jumping through many hoops just so a manual tester could press a play button and say "they passed".
It cannot verify non API exposed state
As it can only access the endpoints your API exposes (there are some plugins but none ive seen solve this problem) you can only assert on what is returned to you via your request, so what if you want to verify that a success has put a message in a queue, or added an audit log in a database, or even that some computed data has been correctly triggered etc.
You could expose "test" endpoints that are somewhat hidden but thats a rubbish solution and a security concern, also its a waste of effort having to make bespoke endpoints JUST for this 3rd party tool to do something you could do yourself far easier.
Automation/Reporting requires effort
Most devs/testers will be using the tool manually, however if you are using a build pipeline and want to run it as part of that you will need to use their automated runner Newman which can be a bit tricky to setup as you need to feed it specific vars contextual to postmans environments and test ids etc, once you have them you also may struggle to get the reports out in a format that you want as well as potential issues when the environment you are wanting to run it on may not be externally accessible for whatever reasons.
IT COSTS MONEY
$14 per user per month with usage limits for the most basic tier, why would you pay this when chances are you already have devs who could do this better and for cheaper.
Moving away from Postman/3rd Party Tools
The solution to this problem is not a complex one, you just move the tests into your code repository using your existing unit test runner to orchestrate, but add in a RESTful HTTP lib or the built in one to do calls. We already have a rich data layer we can make use of without having to hand roll any data integration code, although you are free to write your own bespoke stuff separately.
If we revisit the example of creating a user mentioned above in code, we could write a test like so:
That was fairly easy right? we just called the DB added a user in with the email address, then called create with that email address and verified it was a 409 response.
I have glossed over some pain points here, like how we populate the EnvironmentConfig
and how we tidy up data after tests are run, authentication etc, but if we ignore that for now there is nothing really magical happening here, if we were to do this with postman we would need to setup the environment stuff too. We wouldnt have access to the DB so would need to just all create before or in a separate test and hope it worked, as well as doing some js code internal to the test to verify bits, all to do the same thing as like few lines of code.
I very much wanted to put this meme here, I hope you enjoy it as much as I do
THE HUGE BENEFITS OF THIS
Let me mention before we continue that this way has sooooooo many upsides:
You the can run these tests locally, on a build server, wherever you want (more complex, but will get to that later)
Tests are atomic and can be run in any order or even in parallel in some cases
All tests are part of the code repo and undergo same PR/Branching rules as rest of the code
Tests can be run before code is comitted/pushed/whatever
It didnt cost you a penny (ignore developer time, which I would argue would end up being more time in Postman anyway)
Runs VERY fast as it can go directly to data without needing to do HTTP hops/overhead
You can verify anything via code, check an event went into a queue, an object went into S3/Azure Blob etc
Also you are basically writing a huge regression suite that acts as a safety net for any dev who wants to start doing new work, they pull down their repo, they run all the tests and if it all works, they know they are good to go. You also know the tests should all be passing as its in source control and is probably plugged into your build pipeline so your build wouldnt pass if the tests didnt work.
This last point is somewhat true for both sides, but anecdotally almost all postman tests ive seen end up being flakey as they get more complex in nature so often they ended up getting turned off on builds or at least flagged as non failing if tests dont pass for whatever reason.
The New Problems
So as I mentioned in the previous sections there are some problems that you will come up against, and these problems exist in both worlds but you need to solve them in code rather than in postman/whatever.
Environment Data
The first major problem you will face is how to get the environment running and setup the environment config. You can hardcode stuff to run on your localhost to begin with and thats fine, but when you want to run it anywhere you will need to have a config file, and that config file needs to be changed per environment. There are a myriad of ways to do this and you are probably already doing something similar in your build pipeline for secrets etc elsewhere so I would just follow whatever pattern you have there to get access to DB connection strings, api urls, test logins etc.
Getting the environment running can be simple of tricky, if you use docker/containers you can just run all of these tests within a docker compose which runs the tests as well as your api/db etc, but if not you can just manually get it running before you run the tests. Again there are many ways to do this and you probably already do this when you deploy your environments on the build server for testing, so just follow that pattern.
Cleaning Up Data
This can be as complex as you want it to be, but I have had great success making a DataHelper
style class in the test project which wraps the Repository
and tracks what data has been added and automatically removes it at the end of a test being run. As most test runners expose methods/hooks/attributes/interfaces/whatever you can use to get a method executed when a test finishes regardless of it being a success or failure.
You can also use this for streamlining creation of test data which can make your tests more succinct and self cleaning, for example a really basic thing would be:
You inject this in wherever you want it and then after your test has complete, be it via XUnit fixtures or NUnit Setup or whatever your test running tool uses. As long as you provision your data from the DataHelper
or at least call TrackCreatedData
with any data you are provisioning yourself in your test it should get cleared up automatically.
This may not work 100% depending on how complex your data layer is and if its all databases or queues etc, but assuming you have most of the functionality already in your data layer it should be fairly easy for you to implement.
Getting Testers Involved
All teams are different, some teams only have manual testers, some have manual testers who know postman, and some have more technical testers who know how to code and do testing stuff. The solution to this problem depends a lot on team composition and skillset as well as what level of involvement the testers want.
If we assume minimum involvement, they can just look at the build system output and if that says it passed and they are happy your tests all meet the acceptance criteria then you are good. If they want full involvement and know how to code they can literally work on the tests on your branch with you however you want. In reality though when you adopt this sort of approach you are not as dependent on testers, as each dev on the team assumes a small amount of testing responsibility. Personally I find this great as I know before I push anything up it all works as expected, and I have some confidence in the code im checking out, some devs wont like this as its more work for them, but I would argue in the long run it will save you time and having to faff with the test team heckling you about stuff.
There is some middle ground though, with an approach known as BDD
which I have a whole section about in the writing-requriements
chapter, and for the previous test example it would look something like this:
Thats pretty succinct right? and we even did a more complex 2nd test there assuming we needed to hook into the response data, the database data, an event bus/queue. You couldnt simple do any of that latter test with postman, it would let you interrogate the response object but you couldnt go much deeper, so you would not have much confidence the whole AC was met without further testing, be it manual or integration tests etc.
Assuming the team as a whole (the business, the testers, the devs etc) are happy enough to all use this syntax (known as gherkin
) then you can get the testers and business to write the above gherkin syntax and the devs (and maybe testers) write the underlying steps which make it run, which we will cover in the next section.
Also notice I didnt have a
Given
stage on the 2nd test, didnt need one, you dont always need to setup data before hand, so dont worry if you dont have anything to setup, although you may also need to add lines likeGiven I am already logged in
which would cover the auth part of the api in real world scenarios etc.
How to get the BDD stuff running?
It's all well and good me showing you some BDD stuff and saying its great but how do you actually use it?
In .net there is
Specflow
, in js there isCucumber.js
and many other Cucumber variants on other platforms, although here we will just focus on Specflow and the high level bits, you can follow their docs on how to setup the runner and infrastructure bits.
The way it works is that you have 2 parts, the *.spec
files which contain the gherkin syntax for the tests, then the Step
classes which have the pattern matching logic, for example if we wanted to target the above bits:
I havent modelled every step but that should be good enough as an example, its pretty easy to use and you can expose as many steps with flexible re-usable syntax as you want via regex. So you can potentially make a load of steps for common Api interactions and then just let people compose them for new tests without needing to write new steps.
This is not always going to be the best solution for every team but it can work really well if everyone is on the same page. If the team/company is more dev heavy and have less testers then maybe you dont need to put in the extra effort for the BDD part but its always something to think about if you want to align your requirements and test criteria in a common way.
Regardless of how you do it be it with BDD or in raw code, you are often going to find tests are more valuble and more stable, faster and consistent. It also allows testing closer to the implementation point and more flexibility on how and what you test, so stop using Postman as a crutch as you honestly don't need it for the most part.
Last updated