Here you’ll find an approach to cover your Spring Cloud Gateway Filters with tests.
Intro
There are so many articles on internet about how to use Spring Gateway.
- https://spring.io/guides/gs/gateway/
- https://www.baeldung.com/spring-cloud-gateway
- https://blog.knoldus.com/spring-cloud-api-gateway/
- https://www.educba.com/spring-cloud-gateway/
- https://medium.com/@niral22/spring-cloud-gateway-tutorial-5311ddd59816
- and others
There are many articles on internet about how to implement authorization inside Spring Gateway.
- https://medium.com/@niral22/spring-cloud-gateway-tutorial-5311ddd59816
- https://oril.co/blog/spring-cloud-gateway-security-with-jwt/
- https://www.baeldung.com/spring-cloud-gateway-oauth2
- https://www.xoriant.com/blog/microservices-security-using-jwt-authentication-gateway
But none of them explain how to cover your code with tests. Let me fill this gap up.
Few words about Spring Cloud Gateway
What is Spring Gateway in a nutshell? Reading the official documentation helps to get the sense.
This project provides a library for building an API Gateway on top of Spring WebFlux. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
https://spring.io/projects/spring-cloud-gateway
Simply saying Spring Cloud Gateway is a manageable router. The setup of routing can be specified in Java and application.properties (or .yaml) file both.
Here is a Java routing settings:
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
Code language: Java (java)
And here is another one example of placing it in application.yaml:
spring:
cloud:
gateway:
routes:
- id: card-api
uri: http://card-api/
predicates:
- Path=/card/**
- id: payment-api
uri: http://payment-api/
predicates:
- Path=/payment/**
- id: wallet-api
uri: http://wallet-api/
predicates:
- Path=/wallet/**
Code language: JavaScript (javascript)
But for me the main feature of Spring Cloud Gateway is the possibility of being as authorization point of all incoming requests.
The main point where all requests can be intercepted for reasons of changing and analyzing is Filter. All filters in Spring Web linked in one filter chain and Spring Cloud Gateway is no different. Spring Web differs of Spring Cloud Gateway in part of thread model of processing incoming requests. Spring Web implemented in the paradigm of old thread model where each request consumes only one dedicated thread. And Spring Cloud Gateway implemented in reactive paradigm – listening thread does not block while waiting the response of server.
Reactivity – what does it bring to us? Non blocking processing as an increase of productivity – the first. But the other side of this boost are restrictions in testing.
How to test Spring Cloud Gateway?
Here is probably only one article on the internet referring to this question- https://stackoverflow.com/questions/59825774/how-to-unit-test-springs-gateway
The approved answer is to write an API simulator or Proxy to fakely response to each request.

But that post on SOF does not answer properly on how to cover Spring Cloud Gateway Filter with tests!
Proper way to cover Spring Cloud Gateway Filter with tests
Here is the schema of request passing through the Spring Cloud Gateway

In order to encapsulate test flow and make it more unit any fake web server should be used as a destination of requests. Otherwise tests will be failed with 500 Internal Server error due to destination unavailability. Good option is WireMock. Simple test class using WireMock is like:
@WireMockTest
public class DeclarativeWireMockTest {
@Test
void test_something_with_wiremock(WireMockRuntimeInfo wmRuntimeInfo) {
// The static DSL will be automatically configured for you
stubFor(get("/static-dsl").willReturn(ok()));
// Instance DSL can be obtained from the runtime info parameter
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(get("/instance-dsl").willReturn(ok()));
// Info such as port numbers is also available
int port = wmRuntimeInfo.getHttpPort();
// Do some testing...
}
}
Code language: Java (java)
Trait of Reactive application to be kept in mind when testing
Another point to be taken in a view is thread model. If you write tests which access database like I do, then you should avoid database in tests on Spring Cloud Gateway.
In tests covering old MVC Spring there was an option to work with database – annotating the test with @Transactional
annotation. This approach leads to all datum created for testing will be deleted right after the test automatically. This simple example of this approach is in code below. @Transactional
on class or class method opens transaction before the test and do rollback right after the test automatically.
@Transactional
@SpringBootTest
@AutoconfigureMockMvc
class TestMvc {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@Test
void testMockMvc() throws Exception {
// given
createAllEntities();
// when
mockMvc.perform(post("...")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsBytes(createRequest())))
// then
.andExpect(status().isOk());
}
}
Code language: Java (java)
But this approach does not work in reactive Spring applications. And that’s why.
Take a look at the schema above. The Spring Cloud Gateway block creates new thread for processing every request incoming. Therefore the code of gateway (including the authentication filter) has no possibility to read the datum previously written into database when transaction is not committed.
Breakpoint in the beginning of the test shows following:

Breakpoint in Filter shows the following:

It shows that in reactive gateway application the request is caught in separated thread.
Hint to deal with database in testing Reactive Spring Cloud Gateway
As known, when the application interacts with database, the test covering that application must:
- Prepare all data need for test
- Perform a call to the application
- Remove all data created previously
The easiest way to achieve this is to run test in a transaction. When the application interacts with database in separated threads it needs to replace or spoof all multi-threading work with single-threaded model. Another way to achieve database cleanliness after testing is to get rid of database access in testing. Yes, you can mock the database.
Mocking the database works fine in couple with mocking destination server in testing Spring Cloud Gateway. The simple test is below.
@Configuration
public class SimpleRouter {
@Bean
public RouteLocator locator(RouteLocatorBuilder builder) {
return builder.routes()
.route("stub-route", r -> r.path("/stub")
.uri("http://localhost:8080/stub")
)
.build();
}
}
@ActiveProfiles("test")
@Transactional
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(OutputCaptureExtension.class)
@AutoConfigureWireMock
public class AuthFilterTest {
@LocalServerPort
int localPort;
WebTestClient webClient;
@BeforeEach
public void setup() {
final String baseUri = "http://localhost:" + localPort;
webClient = WebTestClient.bindToServer()
.defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.responseTimeout(Duration.ofSeconds(100))
.baseUrl(baseUri).build();
}
@Test
void testRegistered() throws Exception {
// given
final String jwt = generateJwt();
walletExists();
stub();
// when
requestLeadsToResponse(setup -> setup.header("Authorization", "Bearer " + jwt),
// then
200, RESPONSE_OK);
}
// mocking gdatabase
void walletExists() {
when(accountRepository.findById(eq(ACCOUNT_ID)))
.thenReturn(Optional.of(aWallet()));
}
// mocking gateway destination
void stub() throws Exception {
stubFor(get(urlEqualTo("/stub"))
.withHeader(HEADER_ACCOUNT_ID, new EqualToPattern(ACCOUNT_ID))
.willReturn(ok(RESPONSE_OK)));
}
void requestLeadsToResponse(Consumer<WebTestClient.RequestHeadersSpec<?>> authorizer, int resultCode, String resultBody) throws Exception {
WebTestClient.RequestHeadersSpec<?> webClientSetup = webClient.get()
.uri("/stub");
authorizer.accept(webClientSetup);
EntityExchangeResult<byte[]> response = webClientSetup.exchange()
.expectBody()
// then
.json(resultBody)
.returnResult();
log.warn("RESPONSE: " + (response.getResponseBody() == null ? "NULL" : new String(response.getResponseBody())));
assertEquals(resultCode, response.getStatus().value());
}
}
Code language: Java (java)
Another option to try is to set READ_UNCOMMITTED level of transaction isolation on test beginning. This case all the data saved in test transaction will be available in another thread in Filter.
Third, unfortunately, I was unable to find a proper way to replace multi-threading model of Reactive Spring Cloud Gateway with single-threaded model for testing. If you can find it, please tell me in comments below or in my telegram blog comments: https://t.me/bvn13_blog