|In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.|
|Dependency injection||Configurations||Nested configurations||Organizing configurations|
|Primary beans||Avoiding problems||Profiles||Mocking I - Lifecycle|
|Mocking II - Reset||Mocking III - MockBean||Data I - @SQL||Data II - JDBC
|Data III - JPA||Controllers I||Controllers II||Consumer tests
In Spring, Controllers allow external consumers to use REST APIs to call into the application logic. This is very convenient, since we don’t need to write a whole translation level. Spring controllers give us full access to the request parameters and headers, and allows us to create a response and return a status.
Up until now, we’ve looked at how to test components under the hood of APIs. Namely, our integration tests created classes-under-test either with “new“, or let Spring inject them directly into the test classes. While those integration tests can give us a nice coverage, it’s not how they will be called in the application.
In our integration tests, we want to go up a level, and try to call the controllers and go in, sometimes deep into the database. Such scenarios gives us the confidence our API works as expected. However, there are some testing considerations.
- Spring configures a web server and runs the full application. In our integration tests, we’d like to not to operate the real application. So we’d like to call a different server, maybe with a different address and port.
- In production mode, everything is up in and running. But in testing mode, we may need just a subset of “things”. Only the beans that interest us, and only the data that’s needed, for testing that one API.
Sometimes we forget to ask a simple question: What’s the value of testing through the API?
Until now, we’ve tested this path:
For the same scenario, we now want to check the following path:
Yes, we can go through the controller, and check that Spring works as expected. But, we’re kind of assuming it does, right?
It’s nice to know the integration fails, but then, there’s not much we can do about it. And we have a different kind of problems.
The next layer is the Controller logic. It can have its own logic, but a better design would be to act only as a gateway for calling into the Application logic. From this point on, our other integration tests take over.
The value in testing the whole path, is to give us confidence that “everything plays together”. However, it should not be the default mode of our integration tests. As we’re stepping up the testing pyramid, longer scenarios should be kept to a minimum. They are expensive to set up and harder to identify problems than short test scenarios.
That means that from design point of view, we should keep the Controller logic to a minimum as well. In most cases, it should transfer Request information in and Response information out. Then, we can just unit test the Controller logic, or write “unit” (Spring integration and mocking the other end) tests just for it before it calls into the Application logic, concentrating on the controller code.
So to summarize, we need the API tests, but we should have less of them than other integration tests and unit tests.
Next time, we’ll see how Spring helps us with Controller integration tests that go all the way to the database.