1. Introduction to End-to-End Tests for API
Hello, everyone. My name is Valentin Kononov, and today my lightning talk is about end-to-end tests for API. How I did it. How it saved my nerves and hours and so on so forth.
At the moment, I'm working at a Unity company, and it's all about fun and games and stuff, but I am working in the advertisement department, so we have a lot of services and both frontend and backends, and of course, we need to test them carefully.
Straight to the point, we don't have too much time. When we talk about API testing, what do we usually mean? First of all, we need to test the data flow, so how the data flows through our services, what are DB queries, so what input, output and data transformers. And in most cases, we just need to test if crude operations work fine and the syndication work fine and all the endpoints are correct, and what we usually do to achieve that. Of course, in most cases, we have integration tests.
And how does it work? Integration tests usually test the whole module, like the whole service, but the service is not something which is present by itself, it's something which communicates with other modules like a database or data repository or some syndication model, something else. So that's why to test it, we need to mock some dependencies. And when we did that and the test is working fine, what do we do next? We update the code, we make some new features. And what do we need to do next? We need to update the mock dependencies, so that the test keeps working. And what else? Yes, we need to update mock dependencies once again, and again, and again, and again.
So I really faced it. We had a really big, important, super mega-critical test about some export process. And every time we changed a minor thing in the export code, we needed to completely rewrite the whole test. That was bad. So, how usually the integration test could look like with all the mocks? So it's actually a huge pile of different kinds of mocking functions, like you see in the slides. Sometimes we mock the whole module, sometimes we mock just one function. And of course, when something is changed in the function, especially in the last example here, the update function was changed, and its output was changed. Even the format of output, like it was an object, became just one simple number. So, we needed to update the mock. And that actually sucked because we spent a lot of time just for supporting the tests. But it should be vice versa, so tests should save all the time for the development. So is there any alternatives of how we can proceed so that the whole process would be easier for us as developers? Basically, my recipe for that was end-to-end tests.
2. End-to-End Tests for API
We discussed using the Nest.js framework for end-to-end tests in API development. End-to-end tests involve starting up the entire API in a testing environment and testing it point by point. The initialization of the Nest API involves creating a testing module, initializing it, and creating a Nest application. Tests can be customized by overriding authentication guards. The tests themselves involve making POST or GET requests to specific endpoints, sending request data, and expecting specific response codes and properties. End-to-end tests have pros such as being easy to fix, modify, and serving as additional documentation for the API. However, writing the initial version of the test can take time, especially when data preparation is required. A trick for mocking the date in tests is also shared.
We discussed with the team, okay, we have Nest.js framework. It has a lot of testing capabilities. So why don't we use it for end-to-end tests? And what is the end-to-end test for when we're talking about the API? It's when you start up the whole API, like the whole node service in your testing environment, and you test it at point by point.
So in this slide, you can see how initialization of the Nest API could look like. So you see, create testing module, you initialize it, you're like, create Nest application. In the end, you should close it and of course you can overwrite anything you want. For example, Nest, we use so-called guards for authentication. Here, we can override it, initialize and close in the very end when all the tests passed. And authentication guard mock is a pretty simple thing, which you just write once and use everywhere. It's really such simple stuff.
And here is how the whole test would look like. So you literally request, you do a POST or GET request to some certain endpoint, for some certain URL. And in this sample, because it's POST, you can send somebody details, like what kind of project data we should add. And you can expect, like here in the last line, you can expect some kind of code, like it should return 201. Which means successfully created. And you can also do a GET request and also form some URL, send requests, expect some code. And also expect some response. And check that the response properties are correct.
What do you mean? So the pros and cons. The pros, easy to fix, easy to modify and you test the whole workflow. And as you see right here the whole test is very readable and it's also an additional source of documentation for your API. But of course there are some cons and bad sides. So it can take time to write the initial version of this test because in some cases you need to prepare the data. Of course when you have just a crude API you can use just only get or post requests to create data and to make sure it's created. But in some cases you need to prepare the data in a database and this minimal database should be run somewhere in a continuous integration service. And just for you we have a really special trick, really small. Sometimes, in my experience, only once. But we needed to fake the date. I prepared some data and I should make sure that this data is created with this date now. And usually we did it like that. But in some code we actually use not just the now function, but also construct. And there is a little bit more comprehensive solution of how to mock the date for both places for now and for the constructor. So just make a screenshot, it's a very useful trick, see you later.
And here's the link for my slides, my website and my doc, which inspired me for all of that stuff. So, as a summary, make tests. It's really a great thing when you do tests. And entering tests makes the changes faster and you can cover the whole workflow with the last mocks and be much more happy.