Spring Cloud and BigQuery and Testcontainers
Spring boot is the most popular java framework for web applications.
Testcontainers is the most popular technology for mocking external dependencies during automated tests.
BigQuery is Google’s cloud data warehouse.
It should be easy to test those in combination.
It’s not!
Let’s start with spring’s sample project for BigQuery. We change the existing test, so that it uses Testcontainers’ BigQuery mock instead of the the real BigQuery implementation from Google.
Problem #1 Override endpoint
There’s no obvious configuration to override the BigQuery endpoint.
Spring boot’s default connection endpoint is a Google URL, which totally makes sense in production.
But there is no built-in option for overriding the endpoint. We need to do this on our own.
Pay attention: com.google.api.gax.rpc.ClientSettings.Builder#setEndpoint
does not like to have the full URL, but just the host and port:
@Bean
@Primary
public BigQuery bigQuery() {
String url = "http://" + httpHostAndPort;
BigQueryOptions options = BigQueryOptions
.newBuilder()
.setProjectId(projectId)
.setHost(url)
.setLocation(url)
.setCredentials(NoCredentials.getInstance())
.build();
return options.getService();
}
@Bean
@Primary
public BigQueryWriteClient bigQueryWriteClient() throws IOException {
return BigQueryWriteClient.create(BigQueryWriteSettings.newBuilder()
.setCredentialsProvider(new NoCredentialsProvider())
.setEndpoint(httpHostAndPort)
.setQuotaProjectId(projectId)
.setHeaderProvider(new UserAgentHeaderProvider(GcpBigQueryAutoConfiguration.class))
.build());
}
Problem #2 No Credentials
Overriding the BigQuery endpoint, does not override the endpoints for Google’s token management. So you might see one of those exceptions:
com.google.api.gax.rpc.UnauthenticatedException: io.grpc.StatusRuntimeException: UNAUTHENTICATED: Failed computing credential metadata
java.io.IOException: Your default credentials were not found. To set up Application Default Credentials for your environment, see https://cloud.google.com/docs/authentication/external/set-up-adc.
Caused by: java.lang.IllegalStateException: OAuth2Credentials instance does not support refreshing the access token. An instance with a new access token should be used, or a derived type that supports refreshing.
at com.google.auth.oauth2.OAuth2Credentials.refreshAccessToken(OAuth2Credentials.java:366)
at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:269)
at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:266)
at java.base/java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:264)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java)
at com.google.auth.oauth2.OAuth2Credentials$RefreshTask.run(OAuth2Credentials.java:633)
at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:31)
at com.google.auth.oauth2.OAuth2Credentials$AsyncRefreshResult.executeIfNew(OAuth2Credentials.java:581)
at com.google.auth.oauth2.OAuth2Credentials.asyncFetch(OAuth2Credentials.java:232)
at com.google.auth.oauth2.OAuth2Credentials.getRequestMetadata(OAuth2Credentials.java:182)
at com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials.getRequestMetadata(QuotaProjectIdHidingCredentials.java:64)
at com.google.auth.Credentials.blockingGetToCallback(Credentials.java:112)
at com.google.auth.Credentials$1.run(Credentials.java:98)
... 3 more
We need to override the CredentialsProvider spring bean:
@Bean
@Primary
public CredentialsProvider credentialsProvider() {
return () -> NoCredentials.getInstance();
}
The whole configuration can be found in https://github.com/joerg-pfruender/springbootbigquerytestcontainers/blob/master/src/test/java/com/example/config/GcpTestConfiguration.java
Problem #3 Ports for testcontainers
When you have solved this, you may receive a java.net.ConnectException
.
Spring Cloud uses the streaming API for BigQuery. Our BigQuery mock image ghcr.io/goccy/bigquery-emulator
supports the streaming API, but with one limitation: It needs to know, on which port it runs.
We need to change the BigQueryEmulatorContainer
, so that the service runs on the same port, on the host AND in the container.
public BigQueryEmulatorContainer(DockerImageName dockerImageName) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
httpPort = findFreePort();
grpcPort = findFreePort();
withCommand("--project="+ PROJECT_ID, "--port="+ httpPort, "--grpc-port="+ grpcPort);
addFixedExposedPort(httpPort, httpPort);
addFixedExposedPort(grpcPort, grpcPort);
}
You can find the complete code on https://github.com/joerg-pfruender/springbootbigquerytestcontainers
Pull Request for testcontainers: https://github.com/testcontainers/testcontainers-java/pull/7788
Any comments or suggestions? Leave an issue or a pull request!