You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2019/06/06 14:08:28 UTC

[camel] 01/03: CAMEL-13617: Improve testability of google-sheets component

This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch camel-2.x
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 12ca5a537e3dfc0eb9b2b02782016f5e15e80991
Author: Christoph Deppisch <cd...@redhat.com>
AuthorDate: Thu Jun 6 12:47:51 2019 +0200

    CAMEL-13617: Improve testability of google-sheets component
---
 components/camel-google-sheets/pom.xml             |  90 ++++-
 .../main/docs/google-sheets-stream-component.adoc  |   2 +-
 .../sheets/BatchGoogleSheetsClientFactory.java     |  63 +++-
 .../google/sheets/GoogleSheetsClientFactory.java   |   6 +-
 .../google/sheets/GoogleSheetsEndpoint.java        |  41 ++-
 .../google/sheets/GoogleSheetsProducer.java        |   2 +-
 .../sheets/GoogleSheetsVerifierExtension.java      |   9 +-
 .../sheets/internal/GoogleSheetsConstants.java     |   4 +-
 .../stream/GoogleSheetsStreamConfiguration.java    |   4 +-
 .../sheets/stream/GoogleSheetsStreamConsumer.java  |  23 +-
 .../sheets/AbstractGoogleSheetsTestSupport.java    |  43 ++-
 .../sheets/SheetsSpreadsheetsIntegrationTest.java  |  37 +-
 .../SheetsSpreadsheetsValuesIntegrationTest.java   |  83 ++++-
 .../sheets/server/GoogleSheetsApiTestServer.java   | 354 ++++++++++++++++++
 .../server/GoogleSheetsApiTestServerAssert.java    | 405 +++++++++++++++++++++
 .../server/GoogleSheetsApiTestServerRule.java      | 116 ++++++
 .../AbstractGoogleSheetsStreamTestSupport.java     |  17 +
 .../SheetsStreamConsumerIntegrationTest.java       |  77 +++-
 .../src/test/resources/googleapis.jks              | Bin 0 -> 2695 bytes
 .../src/test/resources/test-options.properties     |   8 +-
 parent/pom.xml                                     |   1 +
 .../GoogleSheetsStreamComponentConfiguration.java  |   2 +-
 22 files changed, 1281 insertions(+), 106 deletions(-)

diff --git a/components/camel-google-sheets/pom.xml b/components/camel-google-sheets/pom.xml
index d13d94f..0be3151 100644
--- a/components/camel-google-sheets/pom.xml
+++ b/components/camel-google-sheets/pom.xml
@@ -29,8 +29,8 @@
 
   <artifactId>camel-google-sheets</artifactId>
   <packaging>jar</packaging>
-  <name>Camel :: GoogleSheets</name>
-  <description>Camel Component for GoogleSheets</description>
+  <name>Camel :: Google Sheets</name>
+  <description>Camel Component for Google Sheets</description>
 
   <properties>
     <schemeName>google-sheets</schemeName>
@@ -40,8 +40,53 @@
     <camel.osgi.private.pkg>org.apache.camel.component.google.sheets.internal</camel.osgi.private.pkg>
     <camel.osgi.export.pkg>org.apache.camel.component.google.sheets</camel.osgi.export.pkg>
     <camel.osgi.export.service>org.apache.camel.spi.ComponentResolver;component=google-sheets</camel.osgi.export.service>
+    <spring-security-oauth2-version>2.3.6.RELEASE</spring-security-oauth2-version>
   </properties>
 
+  <dependencyManagement>
+    <dependencies>
+        <!-- Test dependencies -->
+        <dependency>
+          <groupId>org.assertj</groupId>
+          <artifactId>assertj-core</artifactId>
+          <version>${assertj-version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.springframework</groupId>
+          <artifactId>spring-webmvc</artifactId>
+          <version>${spring-version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.springframework.security</groupId>
+          <artifactId>spring-security-web</artifactId>
+          <version>${spring-security-version}</version>
+        </dependency>
+        <dependency>
+          <groupId>org.springframework.security.oauth</groupId>
+          <artifactId>spring-security-oauth2</artifactId>
+          <version>${spring-security-oauth2-version}</version>
+        </dependency>
+        <dependency>
+          <groupId>com.consol.citrus</groupId>
+          <artifactId>citrus-core</artifactId>
+          <version>${citrus.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>com.consol.citrus</groupId>
+          <artifactId>citrus-java-dsl</artifactId>
+          <version>${citrus.version}</version>
+          <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>com.consol.citrus</groupId>
+          <artifactId>citrus-http</artifactId>
+          <version>${citrus.version}</version>
+          <scope>test</scope>
+        </dependency>
+    </dependencies>
+  </dependencyManagement>
+
   <dependencies>
     <dependency>
       <groupId>org.apache.camel</groupId>
@@ -78,9 +123,9 @@
 
     <!-- Component API javadoc in provided scope to read API signatures -->
     <dependency>
-        <groupId>com.google.apis</groupId>
-        <artifactId>google-api-services-sheets</artifactId>
-        <version>${google-api-services-sheets-version}</version>
+      <groupId>com.google.apis</groupId>
+      <artifactId>google-api-services-sheets</artifactId>
+      <version>${google-api-services-sheets-version}</version>
       <type>javadoc</type>
       <scope>provided</scope>
     </dependency>
@@ -108,6 +153,41 @@
       <artifactId>camel-test</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security.oauth</groupId>
+      <artifactId>spring-security-oauth2</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.consol.citrus</groupId>
+      <artifactId>citrus-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.consol.citrus</groupId>
+      <artifactId>citrus-java-dsl</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.consol.citrus</groupId>
+      <artifactId>citrus-http</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/components/camel-google-sheets/src/main/docs/google-sheets-stream-component.adoc b/components/camel-google-sheets/src/main/docs/google-sheets-stream-component.adoc
index ac98fdc..9112d6d 100644
--- a/components/camel-google-sheets/src/main/docs/google-sheets-stream-component.adoc
+++ b/components/camel-google-sheets/src/main/docs/google-sheets-stream-component.adoc
@@ -91,7 +91,7 @@ with the following path and query parameters:
 | *clientSecret* (consumer) | Client secret of the sheets application |  | String
 | *includeGridData* (consumer) | True if grid data should be returned. | false | boolean
 | *majorDimension* (consumer) | Specifies the major dimension that results should use.. | ROWS | String
-| *maxResults* (consumer) | Specify the maximum number of returned results. This will limit the number of rows in a returned value range data set or the number of returned value ranges in a batch request. | 10 | int
+| *maxResults* (consumer) | Specify the maximum number of returned results. This will limit the number of rows in a returned value range data set or the number of returned value ranges in a batch request. | 0 | int
 | *range* (consumer) | Specifies the range of rows and columns in a sheet to get data from. |  | String
 | *refreshToken* (consumer) | OAuth 2 refresh token. Using this, the Google Calendar component can obtain a new accessToken whenever the current one expires - a necessity if the application is long-lived. |  | String
 | *scopes* (consumer) | Specifies the level of permissions you want a sheets application to have to a user account. See https://developers.google.com/identity/protocols/googlescopes for more info. |  | List
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/BatchGoogleSheetsClientFactory.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/BatchGoogleSheetsClientFactory.java
index 1c5a91a..4125083 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/BatchGoogleSheetsClientFactory.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/BatchGoogleSheetsClientFactory.java
@@ -18,49 +18,78 @@ package org.apache.camel.component.google.sheets;
 
 import com.google.api.client.auth.oauth2.Credential;
 import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.http.HttpTransport;
 import com.google.api.client.http.javanet.NetHttpTransport;
 import com.google.api.client.json.jackson2.JacksonFactory;
 import com.google.api.services.sheets.v4.Sheets;
 import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.util.ObjectHelper;
 
 public class BatchGoogleSheetsClientFactory implements GoogleSheetsClientFactory {
 
-    private final NetHttpTransport transport;
+    private final HttpTransport transport;
     private final JacksonFactory jsonFactory;
 
     public BatchGoogleSheetsClientFactory() {
-        this.transport = new NetHttpTransport();
-        this.jsonFactory = new JacksonFactory();
+        this(new NetHttpTransport(), new JacksonFactory());
+    }
+
+    public BatchGoogleSheetsClientFactory(HttpTransport httpTransport) {
+        this(httpTransport, new JacksonFactory());
+    }
+
+    public BatchGoogleSheetsClientFactory(HttpTransport httpTransport, JacksonFactory jacksonFactory) {
+        this.transport = httpTransport;
+        this.jsonFactory = jacksonFactory;
     }
 
     @Override
-    public Sheets makeClient(String clientId, String clientSecret, String applicationName, String refreshToken, String accessToken) {
+    public Sheets makeClient(String clientId,
+                             String clientSecret,
+                             String applicationName,
+                             String refreshToken,
+                             String accessToken) {
         if (clientId == null || clientSecret == null) {
             throw new IllegalArgumentException("clientId and clientSecret are required to create Google Sheets client.");
         }
+
         try {
-            Credential credential = authorize(clientId, clientSecret);
+            Credential credential = authorize(clientId, clientSecret, refreshToken, accessToken);
 
-            if (refreshToken != null && !"".equals(refreshToken)) {
-                credential.setRefreshToken(refreshToken);
-            }
-            if (accessToken != null && !"".equals(accessToken)) {
-                credential.setAccessToken(accessToken);
-            }
-            return new Sheets.Builder(transport, jsonFactory, credential)
-                                .setApplicationName(applicationName)
-                                .build();
+            Sheets.Builder clientBuilder = new Sheets.Builder(transport, jsonFactory, credential)
+                                                     .setApplicationName(applicationName);
+            configure(clientBuilder);
+            return clientBuilder.build();
         } catch (Exception e) {
             throw new RuntimeCamelException("Could not create Google Sheets client.", e);
         }
     }
 
+    /**
+     * Subclasses may add customized configuration to client builder.
+     * @param clientBuilder
+     */
+    protected void configure(Sheets.Builder clientBuilder) {
+        clientBuilder.setRootUrl(Sheets.DEFAULT_ROOT_URL);
+    }
+
     // Authorizes the installed application to access user's protected data.
-    private Credential authorize(String clientId, String clientSecret) {
+    private Credential authorize(String clientId, String clientSecret, String refreshToken, String accessToken) {
         // authorize
-        return new GoogleCredential.Builder()
+        Credential credential = new GoogleCredential.Builder()
                         .setJsonFactory(jsonFactory)
                         .setTransport(transport)
-                        .setClientSecrets(clientId, clientSecret).build();
+                        .setClientSecrets(clientId, clientSecret)
+                        .build();
+
+        if (ObjectHelper.isNotEmpty(refreshToken)) {
+            credential.setRefreshToken(refreshToken);
+        }
+
+        if (ObjectHelper.isNotEmpty(accessToken)) {
+            credential.setAccessToken(accessToken);
+        }
+
+        return credential;
     }
 }
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsClientFactory.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsClientFactory.java
index 78b4e97..8186790 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsClientFactory.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsClientFactory.java
@@ -20,6 +20,10 @@ import com.google.api.services.sheets.v4.Sheets;
 
 public interface GoogleSheetsClientFactory {
 
-    Sheets makeClient(String clientId, String clientSecret, String applicationName, String refreshToken, String accessToken);
+    Sheets makeClient(String clientId,
+                      String clientSecret,
+                      String applicationName,
+                      String refreshToken,
+                      String accessToken);
 
 }
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsEndpoint.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsEndpoint.java
index a6d1768..f1e2e08 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsEndpoint.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsEndpoint.java
@@ -35,23 +35,32 @@ import org.apache.camel.util.component.ApiMethodPropertiesHelper;
 /**
  * The google-sheets component provides access to Google Sheets.
  */
-@UriEndpoint(firstVersion = "2.23.0", scheme = "google-sheets", title = "Google Sheets", 
-             syntax = "google-sheets:apiName/methodName", consumerClass = GoogleSheetsConsumer.class, consumerPrefix = "consumer", label = "api,cloud,sheets")
+@UriEndpoint(firstVersion = "2.23.0",
+        scheme = "google-sheets",
+        title = "Google Sheets",
+        syntax = "google-sheets:apiName/methodName",
+        consumerClass = GoogleSheetsConsumer.class,
+        consumerPrefix = "consumer",
+        label = "api,cloud,sheets")
 public class GoogleSheetsEndpoint extends AbstractApiEndpoint<GoogleSheetsApiName, GoogleSheetsConfiguration> {
 
-    @UriParam
-    private GoogleSheetsConfiguration configuration;
+    @UriParam(name = "configuration")
+    private GoogleSheetsConfiguration endpointConfiguration;
 
     private Object apiProxy;
 
-    public GoogleSheetsEndpoint(String uri, GoogleSheetsComponent component, GoogleSheetsApiName apiName, String methodName, GoogleSheetsConfiguration endpointConfiguration) {
+    public GoogleSheetsEndpoint(String uri,
+                                GoogleSheetsComponent component,
+                                GoogleSheetsApiName apiName,
+                                String methodName,
+                                GoogleSheetsConfiguration endpointConfiguration) {
         super(uri, component, apiName, methodName, GoogleSheetsApiCollection.getCollection().getHelper(apiName), endpointConfiguration);
-        this.configuration = endpointConfiguration;
+        this.endpointConfiguration = endpointConfiguration;
     }
 
     @Override
     public Producer createProducer() throws Exception {
-        return new org.apache.camel.component.google.sheets.GoogleSheetsProducer(this);
+        return new GoogleSheetsProducer(this);
     }
 
     @Override
@@ -79,19 +88,19 @@ public class GoogleSheetsEndpoint extends AbstractApiEndpoint<GoogleSheetsApiNam
     @Override
     protected void afterConfigureProperties() {
         switch (apiName) {
-        case SPREADSHEETS:
-            apiProxy = getClient().spreadsheets();
-            break;
-        case DATA:
-            apiProxy = getClient().spreadsheets().values();
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid API name " + apiName);
+            case SPREADSHEETS:
+                apiProxy = getClient().spreadsheets();
+                break;
+            case DATA:
+                apiProxy = getClient().spreadsheets().values();
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid API name " + apiName);
         }
     }
 
     public Sheets getClient() {
-        return ((GoogleSheetsComponent)getComponent()).getClient(configuration);
+        return ((GoogleSheetsComponent)getComponent()).getClient(endpointConfiguration);
     }
 
     @Override
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsProducer.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsProducer.java
index abc4060..e9419fc 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsProducer.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsProducer.java
@@ -39,7 +39,7 @@ public class GoogleSheetsProducer extends AbstractApiProducer<GoogleSheetsApiNam
 
     @Override
     protected Object doInvokeMethod(ApiMethod method, Map<String, Object> properties) throws RuntimeCamelException {
-        AbstractGoogleClientRequest<?> request = (AbstractGoogleClientRequest)super.doInvokeMethod(method, properties);
+        AbstractGoogleClientRequest<?> request = (AbstractGoogleClientRequest) super.doInvokeMethod(method, properties);
         try {
             TypeConverter typeConverter = getEndpoint().getCamelContext().getTypeConverter();
             for (Entry<String, Object> p : properties.entrySet()) {
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsVerifierExtension.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsVerifierExtension.java
index 26100cf..4ab0cd3 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsVerifierExtension.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/GoogleSheetsVerifierExtension.java
@@ -61,9 +61,12 @@ public class GoogleSheetsVerifierExtension extends DefaultComponentVerifierExten
         try {
             GoogleSheetsConfiguration configuration = setProperties(new GoogleSheetsConfiguration(), parameters);
             GoogleSheetsClientFactory clientFactory = new BatchGoogleSheetsClientFactory();
-            Sheets client = clientFactory.makeClient(configuration.getClientId(), configuration.getClientSecret(), configuration.getApplicationName(),
-                                                     configuration.getRefreshToken(), configuration.getAccessToken());
-            client.spreadsheets().get(Optional.ofNullable(parameters.get("spreadsheetId")).map(Object::toString).orElse(UUID.randomUUID().toString())).execute();
+            Sheets client = clientFactory.makeClient(configuration.getClientId(), configuration.getClientSecret(),
+                    configuration.getApplicationName(),
+                    configuration.getRefreshToken(), configuration.getAccessToken());
+            client.spreadsheets().get(Optional.ofNullable(parameters.get("spreadsheetId"))
+                                              .map(Object::toString)
+                                              .orElse(UUID.randomUUID().toString())).execute();
         } catch (Exception e) {
             ResultErrorBuilder errorBuilder = ResultErrorBuilder.withCodeAndDescription(VerificationError.StandardCode.AUTHENTICATION, e.getMessage())
                 .detail("google_sheets_exception_message", e.getMessage()).detail(VerificationError.ExceptionAttribute.EXCEPTION_CLASS, e.getClass().getName())
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/internal/GoogleSheetsConstants.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/internal/GoogleSheetsConstants.java
index feb9141..0727e33 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/internal/GoogleSheetsConstants.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/internal/GoogleSheetsConstants.java
@@ -29,5 +29,7 @@ public final class GoogleSheetsConstants {
     /**
      * Prevent instantiation.
      */
-    private GoogleSheetsConstants() { }
+    private GoogleSheetsConstants() {
+        super();
+    }
 }
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConfiguration.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConfiguration.java
index 4de5222..7f949ed 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConfiguration.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConfiguration.java
@@ -57,8 +57,8 @@ public class GoogleSheetsStreamConfiguration implements Cloneable {
     @UriParam
     private String spreadsheetId;
 
-    @UriParam(defaultValue = "10")
-    private int maxResults = 10;
+    @UriParam(defaultValue = "0")
+    private int maxResults;
 
     @UriParam
     private String range;
diff --git a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
index 35b8815..7142953 100644
--- a/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
+++ b/components/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
@@ -85,17 +85,30 @@ public class GoogleSheetsStreamConsumer extends ScheduledBatchPollingConsumer {
                     for (ValueRange valueRange : response.getValueRanges()) {
                         AtomicInteger rangeIndex = new AtomicInteger(1);
                         AtomicInteger valueIndex = new AtomicInteger();
-                        valueRange.getValues().stream()
-                            .limit(getConfiguration().getMaxResults())
-                            .map(values -> getEndpoint().createExchange(rangeIndex.get(), valueIndex.incrementAndGet(), valueRange.getRange(), valueRange.getMajorDimension(), values))
-                            .forEach(answer::add);
+                        if (getConfiguration().getMaxResults() > 0) {
+                            valueRange.getValues().stream()
+                                    .limit(getConfiguration().getMaxResults())
+                                    .map(values -> getEndpoint().createExchange(rangeIndex.get(), valueIndex.incrementAndGet(), valueRange.getRange(), valueRange.getMajorDimension(), values))
+                                    .forEach(answer::add);
+                        } else {
+                            valueRange.getValues().stream()
+                                    .map(values -> getEndpoint().createExchange(rangeIndex.get(), valueIndex.incrementAndGet(), valueRange.getRange(), valueRange.getMajorDimension(), values))
+                                    .forEach(answer::add);
+                        }
                         rangeIndex.incrementAndGet();
                     }
                 } else {
                     AtomicInteger rangeIndex = new AtomicInteger();
                     response.getValueRanges()
                             .stream()
-                            .limit(getConfiguration().getMaxResults())
+                            .peek(valueRange -> {
+                                if (getConfiguration().getMaxResults() > 0) {
+                                    valueRange.setValues(valueRange.getValues()
+                                            .stream()
+                                            .limit(getConfiguration().getMaxResults())
+                                            .collect(Collectors.toList()));
+                                }
+                            })
                             .map(valueRange -> getEndpoint().createExchange(rangeIndex.incrementAndGet(), valueRange))
                             .forEach(answer::add);
                 }
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/AbstractGoogleSheetsTestSupport.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/AbstractGoogleSheetsTestSupport.java
index d52905c..f1e5eb7 100644
--- a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/AbstractGoogleSheetsTestSupport.java
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/AbstractGoogleSheetsTestSupport.java
@@ -24,6 +24,9 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Random;
 
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.sheets.v4.Sheets;
 import com.google.api.services.sheets.v4.model.Sheet;
 import com.google.api.services.sheets.v4.model.SheetProperties;
 import com.google.api.services.sheets.v4.model.Spreadsheet;
@@ -31,8 +34,12 @@ import com.google.api.services.sheets.v4.model.SpreadsheetProperties;
 import com.google.api.services.sheets.v4.model.ValueRange;
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelExecutionException;
+import org.apache.camel.component.google.sheets.internal.GoogleSheetsConstants;
+import org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServer;
+import org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServerRule;
 import org.apache.camel.test.junit4.CamelTestSupport;
 import org.apache.camel.util.IntrospectionSupport;
+import org.junit.ClassRule;
 
 /**
  * Abstract base class for GoogleSheets Integration tests generated by Camel
@@ -45,13 +52,16 @@ public class AbstractGoogleSheetsTestSupport extends CamelTestSupport {
 
     private Spreadsheet spreadsheet;
 
+    @ClassRule
+    public static GoogleSheetsApiTestServerRule googleSheetsApiTestServerRule = new GoogleSheetsApiTestServerRule(TEST_OPTIONS_PROPERTIES);
+
     /**
      * Create test spreadsheet that is used throughout all tests.
      */
     private void createTestSpreadsheet() {
         Spreadsheet spreadsheet = new Spreadsheet();
         SpreadsheetProperties spreadsheetProperties = new SpreadsheetProperties();
-        spreadsheetProperties.setTitle("camel-sheets-" + Math.abs(new Random().nextInt()));
+        spreadsheetProperties.setTitle("camel-sheets-" + new Random().nextInt(Integer.MAX_VALUE));
 
         spreadsheet.setProperties(spreadsheetProperties);
 
@@ -78,12 +88,12 @@ public class AbstractGoogleSheetsTestSupport extends CamelTestSupport {
 
         final Map<String, Object> headers = new HashMap<>();
         // parameter type is String
-        headers.put("CamelGoogleSheets.spreadsheetId", spreadsheet.getSpreadsheetId());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "spreadsheetId", spreadsheet.getSpreadsheetId());
         // parameter type is String
-        headers.put("CamelGoogleSheets.range", TEST_SHEET + "!A1:B2");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "range", TEST_SHEET + "!A1:B2");
 
         // parameter type is String
-        headers.put("CamelGoogleSheets.valueInputOption", "USER_ENTERED");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "valueInputOption", "USER_ENTERED");
 
         requestBodyAndHeaders("google-sheets://data/update?inBody=values", valueRange, headers);
     }
@@ -96,8 +106,20 @@ public class AbstractGoogleSheetsTestSupport extends CamelTestSupport {
         final GoogleSheetsConfiguration configuration = new GoogleSheetsConfiguration();
         IntrospectionSupport.setProperties(configuration, getTestOptions());
 
-        // add GoogleSheetsComponent to Camel context
+        // add GoogleSheetsComponent to Camel context and use localhost url
         final GoogleSheetsComponent component = new GoogleSheetsComponent(context);
+        component.setClientFactory(new BatchGoogleSheetsClientFactory(
+                                            new NetHttpTransport.Builder()
+                                                    .trustCertificatesFromJavaKeyStore(
+                                                            getClass().getResourceAsStream("/" + GoogleSheetsApiTestServerRule.SERVER_KEYSTORE),
+                                                            GoogleSheetsApiTestServerRule.SERVER_KEYSTORE_PASSWORD)
+                                                    .build(),
+                                            new JacksonFactory()) {
+            @Override
+            protected void configure(Sheets.Builder clientBuilder) {
+                clientBuilder.setRootUrl(String.format("https://localhost:%s/", googleSheetsApiTestServerRule.getServerPort()));
+            }
+        });
         component.setConfiguration(configuration);
         context.addComponent("google-sheets", component);
 
@@ -148,17 +170,16 @@ public class AbstractGoogleSheetsTestSupport extends CamelTestSupport {
         return spreadsheet;
     }
 
-    public Spreadsheet getSpreadsheetWithTestData() {
-        if (spreadsheet == null) {
-            createTestSpreadsheet();
-        }
-
+    public Spreadsheet applyTestData(Spreadsheet spreadsheet) {
         createTestData();
-
         return spreadsheet;
     }
 
     public void setSpreadsheet(Spreadsheet sheet) {
         this.spreadsheet = sheet;
     }
+
+    public GoogleSheetsApiTestServer getGoogleApiTestServer() {
+        return googleSheetsApiTestServerRule.getGoogleApiTestServer();
+    }
 }
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsIntegrationTest.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsIntegrationTest.java
index 800d023..b42fbaa 100644
--- a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsIntegrationTest.java
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsIntegrationTest.java
@@ -27,14 +27,16 @@ import com.google.api.services.sheets.v4.model.Request;
 import com.google.api.services.sheets.v4.model.Spreadsheet;
 import com.google.api.services.sheets.v4.model.SpreadsheetProperties;
 import com.google.api.services.sheets.v4.model.UpdateSpreadsheetPropertiesRequest;
-
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.google.sheets.internal.GoogleSheetsApiCollection;
+import org.apache.camel.component.google.sheets.internal.GoogleSheetsConstants;
 import org.apache.camel.component.google.sheets.internal.SheetsSpreadsheetsApiMethod;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServerAssert.assertThatGoogleApi;
+
 /**
  * Test class for {@link com.google.api.services.sheets.v4.Sheets.Spreadsheets} APIs.
  */
@@ -45,14 +47,18 @@ public class SheetsSpreadsheetsIntegrationTest extends AbstractGoogleSheetsTestS
 
     @Test
     public void testCreate() throws Exception {
-        String title = "camel-sheets-" + Math.abs(new Random().nextInt());
+        String title = "camel-sheets-" + new Random().nextInt(Integer.MAX_VALUE);
         Spreadsheet sheetToCreate = new Spreadsheet();
         SpreadsheetProperties sheetProperties = new SpreadsheetProperties();
         sheetProperties.setTitle(title);
 
         sheetToCreate.setProperties(sheetProperties);
 
-        // using com.google.api.services.sheets.v4.model.Spreadsheet message body for single parameter "content"
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasTitle(title)
+                .andReturnRandomSpreadsheet();
+
         final Spreadsheet result = requestBody("direct://CREATE", sheetToCreate);
 
         assertNotNull("create result is null", result);
@@ -63,8 +69,17 @@ public class SheetsSpreadsheetsIntegrationTest extends AbstractGoogleSheetsTestS
 
     @Test
     public void testGet() throws Exception {
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnRandomSpreadsheet();
+
         Spreadsheet testSheet = getSpreadsheet();
 
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .getSpreadsheetRequest(testSheet.getSpreadsheetId())
+                .andReturnSpreadsheet(testSheet);
+
         // using String message body for single parameter "spreadsheetId"
         final Spreadsheet result = requestBody("direct://GET", testSheet.getSpreadsheetId());
 
@@ -76,14 +91,24 @@ public class SheetsSpreadsheetsIntegrationTest extends AbstractGoogleSheetsTestS
 
     @Test
     public void testBatchUpdate() throws Exception {
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnRandomSpreadsheet();
+
         Spreadsheet testSheet = getSpreadsheet();
         String updateTitle = "updated-" + testSheet.getProperties().getTitle();
 
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .batchUpdateSpreadsheetRequest(testSheet.getSpreadsheetId())
+                .updateTitle(updateTitle)
+                .andReturnUpdated();
+
         final Map<String, Object> headers = new HashMap<>();
         // parameter type is String
-        headers.put("CamelGoogleSheets.spreadsheetId", testSheet.getSpreadsheetId());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "spreadsheetId", testSheet.getSpreadsheetId());
         // parameter type is com.google.api.services.sheets.v4.model.BatchUpdateSpreadsheetRequest
-        headers.put("CamelGoogleSheets.batchUpdateSpreadsheetRequest", new BatchUpdateSpreadsheetRequest()
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "batchUpdateSpreadsheetRequest", new BatchUpdateSpreadsheetRequest()
                                                                             .setIncludeSpreadsheetInResponse(true)
                                                                             .setRequests(Collections.singletonList(new Request().setUpdateSpreadsheetProperties(new UpdateSpreadsheetPropertiesRequest()
                                                                                     .setProperties(new SpreadsheetProperties().setTitle(updateTitle))
@@ -91,7 +116,7 @@ public class SheetsSpreadsheetsIntegrationTest extends AbstractGoogleSheetsTestS
 
         final BatchUpdateSpreadsheetResponse result = requestBodyAndHeaders("direct://BATCHUPDATE", null, headers);
 
-        assertNotNull("batchUpdate result in null", result);
+        assertNotNull("batchUpdate result is null", result);
         assertEquals(updateTitle, result.getUpdatedSpreadsheet().getProperties().getTitle());
 
         LOG.debug("batchUpdate: " + result);
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsValuesIntegrationTest.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsValuesIntegrationTest.java
index 0075f7a..405ae7a 100644
--- a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsValuesIntegrationTest.java
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/SheetsSpreadsheetsValuesIntegrationTest.java
@@ -21,6 +21,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 import com.google.api.services.sheets.v4.model.AppendValuesResponse;
 import com.google.api.services.sheets.v4.model.ClearValuesRequest;
@@ -28,14 +29,17 @@ import com.google.api.services.sheets.v4.model.ClearValuesResponse;
 import com.google.api.services.sheets.v4.model.Spreadsheet;
 import com.google.api.services.sheets.v4.model.UpdateValuesResponse;
 import com.google.api.services.sheets.v4.model.ValueRange;
-
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.google.sheets.internal.GoogleSheetsApiCollection;
+import org.apache.camel.component.google.sheets.internal.GoogleSheetsConstants;
 import org.apache.camel.component.google.sheets.internal.SheetsSpreadsheetsValuesApiMethod;
+import org.apache.camel.util.ObjectHelper;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServerAssert.assertThatGoogleApi;
+
 /**
  * Test class for {@link com.google.api.services.sheets.v4.Sheets.Spreadsheets.Values} APIs.
  */
@@ -46,44 +50,63 @@ public class SheetsSpreadsheetsValuesIntegrationTest extends AbstractGoogleSheet
 
     @Test
     public void testGet() throws Exception {
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnRandomSpreadsheet();
+
         Spreadsheet testSheet = getSpreadsheet();
 
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .getValuesRequest(testSheet.getSpreadsheetId(), TEST_SHEET + "!A1:B2")
+                .andReturnValues(Collections.emptyList());
+
         final Map<String, Object> headers = new HashMap<>();
         // parameter type is String
-        headers.put("CamelGoogleSheets.spreadsheetId", testSheet.getSpreadsheetId());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "spreadsheetId", testSheet.getSpreadsheetId());
         // parameter type is String
-        headers.put("CamelGoogleSheets.range", TEST_SHEET + "!A1:B2");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "range", TEST_SHEET + "!A1:B2");
 
         final ValueRange result = requestBodyAndHeaders("direct://GET", null, headers);
 
         assertNotNull("get result is null", result);
         assertEquals(TEST_SHEET + "!A1:B2", result.getRange());
-        assertNull("expected empty value range but found entries", result.getValues());
+        assertTrue("expected empty value range but found entries", ObjectHelper.isEmpty(result.getValues()));
 
         LOG.debug("get: " + result);
     }
 
     @Test
     public void testUpdate() throws Exception {
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnRandomSpreadsheet();
+
         Spreadsheet testSheet = getSpreadsheet();
 
         List<List<Object>> data = Arrays.asList(
                 Arrays.asList("A1", "B1"),
                 Arrays.asList("A2", "B2")
         );
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .updateValuesRequest(testSheet.getSpreadsheetId(), TEST_SHEET + "!A1:B2", data)
+                .andReturnUpdateResponse();
+
         ValueRange values = new ValueRange();
         values.setValues(data);
 
         final Map<String, Object> headers = new HashMap<>();
         // parameter type is String
-        headers.put("CamelGoogleSheets.spreadsheetId", testSheet.getSpreadsheetId());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "spreadsheetId", testSheet.getSpreadsheetId());
         // parameter type is String
-        headers.put("CamelGoogleSheets.range", TEST_SHEET + "!A1:B2");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "range", TEST_SHEET + "!A1:B2");
         // parameter type is com.google.api.services.sheets.v4.model.ValueRange
-        headers.put("CamelGoogleSheets.values", values);
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "values", values);
 
         // parameter type is String
-        headers.put("CamelGoogleSheets.valueInputOption", "USER_ENTERED");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "valueInputOption", "USER_ENTERED");
 
         final UpdateValuesResponse result = requestBodyAndHeaders("direct://UPDATE", null, headers);
 
@@ -98,18 +121,29 @@ public class SheetsSpreadsheetsValuesIntegrationTest extends AbstractGoogleSheet
 
     @Test
     public void testAppend() throws Exception {
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnRandomSpreadsheet();
+
         Spreadsheet testSheet = getSpreadsheet();
 
+        List<List<Object>> data = Collections.singletonList(Arrays.asList("A10", "B10", "C10"));
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .appendValuesRequest(testSheet.getSpreadsheetId(), TEST_SHEET + "!A10", data)
+                .andReturnAppendResponse(TEST_SHEET + "!A10:C10");
+
         final Map<String, Object> headers = new HashMap<>();
         // parameter type is String
-        headers.put("CamelGoogleSheets.spreadsheetId", testSheet.getSpreadsheetId());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "spreadsheetId", testSheet.getSpreadsheetId());
         // parameter type is String
-        headers.put("CamelGoogleSheets.range", TEST_SHEET + "!A10");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "range", TEST_SHEET + "!A10");
         // parameter type is com.google.api.services.sheets.v4.model.ValueRange
-        headers.put("CamelGoogleSheets.values", new ValueRange().setValues(Collections.singletonList(Arrays.asList("A10", "B10", "C10"))));
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "values", new ValueRange().setValues(data));
 
         // parameter type is String
-        headers.put("CamelGoogleSheets.valueInputOption", "USER_ENTERED");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "valueInputOption", "USER_ENTERED");
 
         final AppendValuesResponse result = requestBodyAndHeaders("direct://APPEND", null, headers);
 
@@ -124,15 +158,32 @@ public class SheetsSpreadsheetsValuesIntegrationTest extends AbstractGoogleSheet
 
     @Test
     public void testClear() throws Exception {
-        Spreadsheet testSheet = getSpreadsheetWithTestData();
+        String spreadsheetId = UUID.randomUUID().toString();
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnSpreadsheet(spreadsheetId);
+
+        Spreadsheet testSheet = getSpreadsheet();
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .updateValuesRequest(spreadsheetId, TEST_SHEET + "!A1:B2", Arrays.asList(Arrays.asList("a1", "b1"), Arrays.asList("a2", "b2")))
+                .andReturnUpdateResponse();
+
+        applyTestData(testSheet);
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .clearValuesRequest(testSheet.getSpreadsheetId(), TEST_SHEET + "!A1:B2")
+                .andReturnClearResponse(TEST_SHEET + "!A1:B2");
 
         final Map<String, Object> headers = new HashMap<>();
         // parameter type is String
-        headers.put("CamelGoogleSheets.spreadsheetId", testSheet.getSpreadsheetId());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "spreadsheetId", testSheet.getSpreadsheetId());
         // parameter type is String
-        headers.put("CamelGoogleSheets.range", TEST_SHEET + "!A1:B2");
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "range", TEST_SHEET + "!A1:B2");
         // parameter type is com.google.api.services.sheets.v4.model.ClearValuesRequest
-        headers.put("CamelGoogleSheets.clearValuesRequest", new ClearValuesRequest());
+        headers.put(GoogleSheetsConstants.PROPERTY_PREFIX + "clearValuesRequest", new ClearValuesRequest());
 
         final ClearValuesResponse result = requestBodyAndHeaders("direct://CLEAR", null, headers);
 
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServer.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServer.java
new file mode 100644
index 0000000..00bb91c
--- /dev/null
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServer.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.sheets.server;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.zip.GZIPInputStream;
+
+import com.consol.citrus.Citrus;
+import com.consol.citrus.dsl.runner.DefaultTestRunner;
+import com.consol.citrus.dsl.runner.TestRunner;
+import com.consol.citrus.exceptions.CitrusRuntimeException;
+import com.consol.citrus.http.server.HttpServer;
+import com.consol.citrus.http.server.HttpServerBuilder;
+import com.consol.citrus.http.servlet.GzipHttpServletResponseWrapper;
+import com.consol.citrus.http.servlet.RequestCachingServletFilter;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
+import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
+import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter;
+import org.springframework.security.oauth2.provider.client.BaseClientDetails;
+import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
+import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
+import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+public class GoogleSheetsApiTestServer {
+
+    private static Citrus citrus = Citrus.newInstance();
+
+    private final HttpServer httpServer;
+    private TestRunner runner;
+
+    /**
+     * Prevent direct instantiation.
+     */
+    private GoogleSheetsApiTestServer(HttpServer httpServer) {
+        super();
+        this.httpServer = httpServer;
+    }
+
+    /**
+     * Initialize new test run.
+     */
+    public void init() {
+        runner = new DefaultTestRunner(citrus.getApplicationContext(), citrus.createTestContext());
+    }
+
+    /**
+     * Stop and reset current test run if any.
+     */
+    public void reset() {
+        if (runner != null) {
+            runner.purgeEndpoints(action -> action.endpoint(httpServer));
+            runner.stop();
+        }
+    }
+
+    /**
+     * Obtains the httpServer.
+     * @return
+     */
+    public HttpServer getHttpServer() {
+        return httpServer;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        httpServer.afterPropertiesSet();
+    }
+
+    public TestRunner getRunner() {
+        return runner;
+    }
+
+    /**
+     * Builder builds server instance from given http server builder adding more setting options in fluent
+     * builder pattern style.
+     */
+    public static class Builder {
+        private final HttpServerBuilder serverBuilder;
+
+        private Path keyStorePath;
+        private String keyStorePassword;
+        private int securePort = 8443;
+
+        private String basePath = "";
+
+        private String clientId;
+        private String clientSecret;
+
+        private String accessToken;
+        private String refreshToken;
+
+        public Builder(HttpServerBuilder serverBuilder) {
+            this.serverBuilder = serverBuilder;
+        }
+
+        public Builder securePort(int securePort) {
+            this.securePort = securePort;
+            return this;
+        }
+
+        public Builder keyStorePath(Path keyStorePath) {
+            this.keyStorePath = keyStorePath;
+            return this;
+        }
+
+        public Builder keyStorePassword(String keyStorePass) {
+            this.keyStorePassword = keyStorePass;
+            return this;
+        }
+
+        public Builder basePath(String basePath) {
+            this.basePath = basePath;
+            return this;
+        }
+
+        public Builder clientId(String clientId) {
+            this.clientId = clientId;
+            return this;
+        }
+
+        public Builder clientSecret(String clientSecret) {
+            this.clientSecret = clientSecret;
+            return this;
+        }
+
+        public Builder accessToken(String accessToken) {
+            this.accessToken = accessToken;
+            return this;
+        }
+
+        public Builder refreshToken(String refreshToken) {
+            this.refreshToken = refreshToken;
+            return this;
+        }
+
+        public GoogleSheetsApiTestServer build() throws Exception {
+            SslContextFactory sslContextFactory = new SslContextFactory(true);
+            sslContextFactory.setKeyStorePath(keyStorePath.toAbsolutePath().toString());
+            sslContextFactory.setKeyStorePassword(keyStorePassword);
+
+            HttpConfiguration parent = new HttpConfiguration();
+            parent.setSecureScheme("https");
+            parent.setSecurePort(securePort);
+            HttpConfiguration httpConfiguration = new HttpConfiguration(parent);
+            httpConfiguration.setCustomizers(Collections.singletonList(new SecureRequestCustomizer()));
+
+            ServerConnector sslConnector = new ServerConnector(new org.eclipse.jetty.server.Server(),
+                    new SslConnectionFactory(sslContextFactory, "http/1.1"),
+                    new HttpConnectionFactory(httpConfiguration));
+            sslConnector.setPort(securePort);
+
+            serverBuilder.connector(sslConnector);
+
+            Map<String, Filter> filterMap = new LinkedHashMap<>();
+            filterMap.put("request-caching-filter", new RequestCachingServletFilter());
+            filterMap.put("gzip-filter", new GzipServletFilter());
+            filterMap.put("oauth2-filter", oauth2Filter());
+
+            Map<String, String> filterMapings = new LinkedHashMap<>();
+            filterMapings.put("oauth2-filter", "/" + Optional.ofNullable(basePath).map(path -> path + "/*").orElse("*"));
+            serverBuilder.filterMappings(filterMapings);
+
+            serverBuilder.filters(filterMap);
+
+            serverBuilder.applicationContext(citrus.getApplicationContext());
+
+            GoogleSheetsApiTestServer server = new GoogleSheetsApiTestServer(serverBuilder.build());
+            server.afterPropertiesSet();
+            return server;
+        }
+
+        private Filter oauth2Filter() {
+            BaseClientDetails clientDetails = new BaseClientDetails();
+            clientDetails.setClientId(clientId);
+            clientDetails.setClientSecret(clientSecret);
+            clientDetails.setAccessTokenValiditySeconds(3000);
+            clientDetails.setAutoApproveScopes(Arrays.asList("read", "write"));
+            clientDetails.setScope(Arrays.asList("read", "write"));
+            clientDetails.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("client_credentials"),
+                                                        new SimpleGrantedAuthority("authorization_code"),
+                                                        new SimpleGrantedAuthority("password"),
+                                                        new SimpleGrantedAuthority("refresh_token")));
+
+            OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter();
+            OAuth2AuthenticationManager oauth2AuthenticationManager = new OAuth2AuthenticationManager();
+
+            InMemoryClientDetailsService clientDetailsService = new InMemoryClientDetailsService();
+            Map<String, ClientDetails> clientDetailsStore = new HashMap<>();
+            clientDetailsStore.put(clientId, clientDetails);
+            clientDetailsService.setClientDetailsStore(clientDetailsStore);
+            oauth2AuthenticationManager.setClientDetailsService(clientDetailsService);
+
+            InMemoryTokenStore tokenStore = new InMemoryTokenStore();
+            AuthorizationRequest authorizationRequest = new AuthorizationRequest();
+            authorizationRequest.setClientId(clientDetails.getClientId());
+            authorizationRequest.setAuthorities(clientDetails.getAuthorities());
+            authorizationRequest.setApproved(true);
+
+            OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), null);
+
+            tokenStore.storeAccessToken(new DefaultOAuth2AccessToken(accessToken), authentication);
+            tokenStore.storeRefreshToken(new DefaultOAuth2RefreshToken(refreshToken), authentication);
+
+            DefaultTokenServices tokenServices = new DefaultTokenServices();
+            tokenServices.setTokenStore(tokenStore);
+            tokenServices.setClientDetailsService(clientDetailsService);
+            tokenServices.setSupportRefreshToken(true);
+            oauth2AuthenticationManager.setTokenServices(tokenServices);
+
+            filter.setAuthenticationManager(oauth2AuthenticationManager);
+            return filter;
+        }
+    }
+
+    private static class GzipServletFilter extends OncePerRequestFilter {
+        @Override
+        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+                                        FilterChain filterChain) throws ServletException, IOException {
+            HttpServletRequest filteredRequest = request;
+            HttpServletResponse filteredResponse = response;
+
+            String contentEncoding = request.getHeader(HttpHeaders.CONTENT_ENCODING);
+            if (contentEncoding != null && contentEncoding.contains("gzip")) {
+                filteredRequest = new GzipHttpServletRequestWrapper(request);
+            }
+
+            String acceptEncoding = request.getHeader(HttpHeaders.ACCEPT_ENCODING);
+            if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
+                filteredResponse = new GzipHttpServletResponseWrapper(response);
+            }
+
+            filterChain.doFilter(filteredRequest, filteredResponse);
+
+            if (filteredResponse instanceof GzipHttpServletResponseWrapper) {
+                ((GzipHttpServletResponseWrapper) filteredResponse).finish();
+            }
+        }
+    }
+
+    private static class GzipHttpServletRequestWrapper extends HttpServletRequestWrapper {
+        /**
+         * Constructs a request adaptor wrapping the given request.
+         *
+         * @param request
+         * @throws IllegalArgumentException if the request is null
+         */
+        public GzipHttpServletRequestWrapper(HttpServletRequest request) {
+            super(request);
+        }
+
+        @Override
+        public ServletInputStream getInputStream() throws IOException {
+            return new GzipServletInputStream(getRequest());
+        }
+
+        /**
+         * Gzip enabled servlet input stream.
+         */
+        private static class GzipServletInputStream extends ServletInputStream {
+            private final GZIPInputStream gzipStream;
+
+            /**
+             * Default constructor using wrapped input stream.
+             *
+             * @param request
+             * @throws IOException
+             */
+            public GzipServletInputStream(ServletRequest request) throws IOException {
+                super();
+                gzipStream = new GZIPInputStream(request.getInputStream());
+            }
+
+            @Override
+            public boolean isFinished() {
+                try {
+                    return gzipStream.available() == 0;
+                } catch (IOException e) {
+                    throw new CitrusRuntimeException("Failed to check gzip intput stream availability", e);
+                }
+            }
+
+            @Override
+            public boolean isReady() {
+                return true;
+            }
+
+            @Override
+            public void setReadListener(final ReadListener readListener) {
+                throw new UnsupportedOperationException("Unsupported operation");
+            }
+
+            @Override
+            public int read() {
+                try {
+                    return gzipStream.read();
+                } catch (IOException e) {
+                    throw new CitrusRuntimeException("Failed to read gzip input stream", e);
+                }
+            }
+
+            @Override
+            public int read(byte[] b) throws IOException {
+                return gzipStream.read(b);
+            }
+
+            @Override
+            public int read(byte[] b, int off, int len) throws IOException {
+                return gzipStream.read(b, off, len);
+            }
+        }
+    }
+}
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServerAssert.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServerAssert.java
new file mode 100644
index 0000000..a89edae
--- /dev/null
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServerAssert.java
@@ -0,0 +1,405 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.sheets.server;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import com.consol.citrus.message.MessageType;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.google.api.services.sheets.v4.model.Spreadsheet;
+import com.google.api.services.sheets.v4.model.ValueRange;
+import org.apache.camel.util.ObjectHelper;
+import org.assertj.core.api.AbstractAssert;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+
+public class GoogleSheetsApiTestServerAssert extends AbstractAssert<GoogleSheetsApiTestServerAssert, GoogleSheetsApiTestServer> {
+
+    private ObjectMapper mapper = new ObjectMapper()
+                .setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, JsonInclude.Include.NON_EMPTY))
+                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+                .enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
+                .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
+                .disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+
+    private GoogleSheetsApiTestServerAssert(GoogleSheetsApiTestServer server) {
+        super(server, GoogleSheetsApiTestServerAssert.class);
+    }
+
+    /**
+     * A fluent entry point to the assertion class.
+     * @param server the target server to perform assertions to.
+     * @return
+     */
+    public static GoogleSheetsApiTestServerAssert assertThatGoogleApi(GoogleSheetsApiTestServer server) {
+        return new GoogleSheetsApiTestServerAssert(server);
+    }
+
+    public GetSpreadsheetAssert getSpreadsheetRequest(String spreadsheetId) {
+        return new GetSpreadsheetAssert(spreadsheetId);
+    }
+
+    public void isRunning() {
+        isRunning(5000, TimeUnit.MILLISECONDS);
+    }
+
+    public void isRunning(long timeout, TimeUnit timeUnit) {
+        ScheduledFuture<?> schedule = null;
+        try {
+            CompletableFuture<Boolean> runningProbe = new CompletableFuture<>();
+            schedule = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
+                if (actual.getHttpServer().isRunning()) {
+                    runningProbe.complete(true);
+                }
+            }, 0, timeout / 10, timeUnit);
+
+            runningProbe.get(timeout, timeUnit);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            throw new IllegalStateException(e);
+        } finally {
+            Optional.ofNullable(schedule)
+                .ifPresent(future -> future.cancel(true));
+        }
+    }
+
+    public class GetSpreadsheetAssert {
+        GetSpreadsheetAssert(String spreadsheetId) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+        }
+
+        public void andReturnSpreadsheet(Spreadsheet spreadsheet) throws IOException {
+            String spreadsheetJson = spreadsheet.toPrettyString();
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .get("/v4/spreadsheets/${spreadsheetId}")),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload(spreadsheetJson))
+            );
+        }
+    }
+
+    public ClearValuesAssert clearValuesRequest(String spreadsheetId, String range) {
+        return new ClearValuesAssert(spreadsheetId, range);
+    }
+
+    public class ClearValuesAssert {
+        ClearValuesAssert(String spreadsheetId, String range) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+            actual.getRunner().createVariable("range", range);
+        }
+
+        public void andReturnClearResponse(String clearedRange) throws IOException {
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .post("/v4/spreadsheets/${spreadsheetId}/values/${range}:clear")),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload("{" +
+                            "\"spreadsheetId\": \"${spreadsheetId}\"," +
+                            "\"clearedRange\": \"" + clearedRange + "\"" +
+                        "}"))
+            );
+        }
+    }
+
+    public UpdateValuesAssert updateValuesRequest(String spreadsheetId, String range, List<List<Object>> data) {
+        return new UpdateValuesAssert(spreadsheetId, range, data);
+    }
+
+    public class UpdateValuesAssert {
+        private final List<List<Object>> data;
+
+        UpdateValuesAssert(String spreadsheetId, String range, List<List<Object>> data) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+            actual.getRunner().createVariable("range", range);
+            this.data = data;
+        }
+
+        public void andReturnUpdateResponse() throws IOException {
+            String valuesJson = mapper.writer().writeValueAsString(data);
+
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .put("/v4/spreadsheets/${spreadsheetId}/values/${range}")
+                    .validate("$.values.toString()", valuesJson)),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload("{" +
+                            "\"spreadsheetId\": \"${spreadsheetId}\"," +
+                            "\"updatedRange\": \"${range}\"," +
+                            "\"updatedRows\": " + data.size() + "," +
+                            "\"updatedColumns\": " + Optional.ofNullable(data.get(0)).map(Collection::size).orElse(0) + "," +
+                            "\"updatedCells\": " + data.size() * Optional.ofNullable(data.get(0)).map(Collection::size).orElse(0) +
+                        "}"))
+            );
+        }
+    }
+
+    public AppendValuesAssert appendValuesRequest(String spreadsheetId, String range, List<List<Object>> data) {
+        return new AppendValuesAssert(spreadsheetId, range, data);
+    }
+
+    public class AppendValuesAssert {
+        private final List<List<Object>> data;
+
+        AppendValuesAssert(String spreadsheetId, String range, List<List<Object>> data) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+            actual.getRunner().createVariable("range", range);
+            this.data = data;
+        }
+
+        public void andReturnAppendResponse(String updatedRange) throws IOException {
+            String valuesJson = mapper.writer().writeValueAsString(data);
+
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .post("/v4/spreadsheets/${spreadsheetId}/values/${range}:append")
+                    .validate("$.values.toString()", valuesJson)),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload("{" +
+                        "\"spreadsheetId\": \"${spreadsheetId}\"," +
+                        "\"updates\":" +
+                            "{" +
+                                "\"spreadsheetId\": \"${spreadsheetId}\"," +
+                                "\"updatedRange\": \"" + updatedRange + "\"," +
+                                "\"updatedRows\": " + data.size() + "," +
+                                "\"updatedColumns\": " + Optional.ofNullable(data.get(0)).map(Collection::size).orElse(0) + "," +
+                                "\"updatedCells\": " + data.size() * Optional.ofNullable(data.get(0)).map(Collection::size).orElse(0) +
+                            "}" +
+                        "}"))
+            );
+        }
+    }
+
+    public GetValuesAssert getValuesRequest(String spreadsheetId, String range) {
+        return new GetValuesAssert(spreadsheetId, range);
+    }
+
+    public class GetValuesAssert {
+        GetValuesAssert(String spreadsheetId, String range) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+            actual.getRunner().createVariable("range", range);
+        }
+
+        public void andReturnValueRange(ValueRange valueRange) throws IOException {
+            String valueJson = valueRange.toPrettyString();
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .get("/v4/spreadsheets/${spreadsheetId}/values/${range}")),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload(valueJson))
+            );
+        }
+
+        public void andReturnValues(List<List<Object>> data) throws JsonProcessingException {
+            String valueRangeJson;
+            if (ObjectHelper.isEmpty(data)) {
+                valueRangeJson = "{" +
+                        "\"range\": \"${range}\"," +
+                        "\"majorDimension\": \"ROWS\"" +
+                    "}";
+            } else {
+                valueRangeJson = "{" +
+                        "\"range\": \"${range}\"," +
+                        "\"majorDimension\": \"ROWS\"," +
+                        "\"values\":" + mapper.writer().writeValueAsString(data) +
+                    "}";
+            }
+
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .get("/v4/spreadsheets/${spreadsheetId}/values/${range}")),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload(valueRangeJson))
+            );
+        }
+    }
+
+    public BatchGetValuesAssert batchGetValuesRequest(String spreadsheetId, String range) {
+        return new BatchGetValuesAssert(spreadsheetId, range);
+    }
+
+    public class BatchGetValuesAssert {
+        BatchGetValuesAssert(String spreadsheetId, String range) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+            actual.getRunner().createVariable("range", range);
+        }
+
+        public void andReturnValues(List<List<Object>> data) throws JsonProcessingException {
+            String valueRangeJson;
+            if (ObjectHelper.isEmpty(data)) {
+                valueRangeJson = "{\"spreadsheetId\": \"${spreadsheetId}\"," +
+                    "\"valueRanges\": [" +
+                        "{" +
+                            "\"range\": \"${range}\"," +
+                            "\"majorDimension\": \"ROWS\"" +
+                        "}" +
+                    "]}";
+            } else {
+                valueRangeJson = "{\"spreadsheetId\": \"${spreadsheetId}\"," +
+                    "\"valueRanges\": [" +
+                        "{" +
+                            "\"range\": \"${range}\"," +
+                            "\"majorDimension\": \"ROWS\"," +
+                            "\"values\":" + mapper.writer().writeValueAsString(data) +
+                        "}" +
+                    "]}";
+            }
+
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .get("/v4/spreadsheets/${spreadsheetId}/values:batchGet")),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload(valueRangeJson))
+            );
+        }
+    }
+
+    public CreateSpreadsheetAssert createSpreadsheetRequest() {
+        return new CreateSpreadsheetAssert();
+    }
+
+    public class CreateSpreadsheetAssert {
+        private String title = "@ignore@";
+        private String sheetTitle;
+
+        public CreateSpreadsheetAssert hasTitle(String title) {
+            this.title = title;
+            return this;
+        }
+
+        public CreateSpreadsheetAssert hasSheetTitle(String sheetTitle) {
+            this.sheetTitle = sheetTitle;
+            return this;
+        }
+
+        public void andReturnRandomSpreadsheet() {
+            andReturnSpreadsheet("citrus:randomString(44)");
+        }
+
+        public void andReturnSpreadsheet(String spreadsheetId) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+            actual.getRunner().createVariable("title", title);
+
+            String spreadsheetJson;
+            if (ObjectHelper.isNotEmpty(sheetTitle)) {
+                actual.getRunner().createVariable("sheetTitle", sheetTitle);
+                spreadsheetJson = "{\"properties\":{\"title\":\"${title}\"},\"sheets\":[{\"properties\":{\"title\":\"${sheetTitle}\"}}]}";
+            } else {
+                spreadsheetJson = "{\"properties\":{\"title\":\"${title}\"}}";
+            }
+
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .post("/v4/spreadsheets")
+                    .name("create.request")
+                    .messageType(MessageType.JSON)
+                    .payload(spreadsheetJson)),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload("{\"spreadsheetId\":\"${spreadsheetId}\",\"properties\":{\"title\":\"citrus:jsonPath(citrus:message(create.request.payload()), '$.properties.title')\"}}"))
+            );
+        }
+    }
+
+    public BatchUpdateSpreadsheetAssert batchUpdateSpreadsheetRequest(String spreadsheetId) {
+        return new BatchUpdateSpreadsheetAssert(spreadsheetId);
+    }
+
+    public class BatchUpdateSpreadsheetAssert {
+        private List<String> fields = new ArrayList<>();
+
+        BatchUpdateSpreadsheetAssert(String spreadsheetId) {
+            actual.getRunner().createVariable("spreadsheetId", spreadsheetId);
+        }
+
+        public BatchUpdateSpreadsheetAssert updateTitle(String title) {
+            actual.getRunner().createVariable("title", title);
+            fields.add("title");
+            return this;
+        }
+
+        public void andReturnUpdated() {
+            actual.getRunner().async().actions(
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .receive()
+                    .post("/v4/spreadsheets/${spreadsheetId}:batchUpdate")
+                    .messageType(MessageType.JSON)
+                    .payload("{" +
+                        "\"includeSpreadsheetInResponse\":true," +
+                        "\"requests\":[" +
+                                "{" +
+                                    "\"updateSpreadsheetProperties\": {" +
+                                    "\"fields\":\"" + String.join(",", fields) + "\"," +
+                                    "\"properties\":{" + fields.stream().map(field -> String.format("\"%s\":\"${%s}\"", field, field)).collect(Collectors.joining(",")) + "}" +
+                                "}" +
+                            "}" +
+                        "]}")),
+                actual.getRunner().http(action -> action.server(actual.getHttpServer())
+                    .send()
+                    .response(HttpStatus.OK)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .payload("{\"spreadsheetId\":\"${spreadsheetId}\",\"updatedSpreadsheet\":{\"properties\":{\"title\":\"${title}\"},\"spreadsheetId\":\"${spreadsheetId}\"}}"))
+            );
+        }
+    }
+}
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServerRule.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServerRule.java
new file mode 100644
index 0000000..7ecdc53
--- /dev/null
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/server/GoogleSheetsApiTestServerRule.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.google.sheets.server;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import com.consol.citrus.dsl.endpoint.CitrusEndpoints;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpStatus;
+import org.springframework.util.SocketUtils;
+
+import static org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServerAssert.assertThatGoogleApi;
+
+public class GoogleSheetsApiTestServerRule implements TestRule {
+
+    public static final String SERVER_KEYSTORE = "googleapis.jks";
+    public static final String SERVER_KEYSTORE_PASSWORD = "secret";
+
+    private GoogleSheetsApiTestServer googleApiTestServer;
+    private int serverPort = SocketUtils.findAvailableTcpPort();
+
+    public GoogleSheetsApiTestServerRule(String optionFile) {
+        try {
+            Map<String, Object> testOptions = getTestOptions(optionFile);
+
+            googleApiTestServer = new GoogleSheetsApiTestServer.Builder(CitrusEndpoints.http()
+                    .server()
+                    .port(serverPort)
+                    .timeout(15000)
+                    .defaultStatus(HttpStatus.REQUEST_TIMEOUT)
+                    .autoStart(true))
+                    .keyStorePath(new ClassPathResource(SERVER_KEYSTORE).getFile().toPath())
+                    .keyStorePassword(SERVER_KEYSTORE_PASSWORD)
+                    .securePort(serverPort)
+                    .clientId(testOptions.get("clientId").toString())
+                    .clientSecret(testOptions.get("clientSecret").toString())
+                    .accessToken(testOptions.get("accessToken").toString())
+                    .refreshToken(testOptions.get("refreshToken").toString())
+                    .build();
+
+            assertThatGoogleApi(googleApiTestServer).isRunning();
+        } catch (Exception e) {
+            throw new IllegalStateException("Error while reading server keystore file", e);
+        }
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new GoogleSheetsApiTestServerStatement(base);
+    }
+
+    /**
+     * Read component configuration from TEST_OPTIONS_PROPERTIES.
+     * @return Map of component options.
+     */
+    private Map<String, Object> getTestOptions(String optionFile) throws IOException {
+        final Properties properties = new Properties();
+        properties.load(getClass().getResourceAsStream(optionFile));
+
+        Map<String, Object> options = new HashMap<>();
+        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+            options.put(entry.getKey().toString(), entry.getValue());
+        }
+
+        return options;
+    }
+
+    /**
+     * Rule statement initializes and resets test server after each method.
+     */
+    private class GoogleSheetsApiTestServerStatement extends Statement {
+        private final Statement base;
+
+        GoogleSheetsApiTestServerStatement( Statement base ) {
+            this.base = base;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            googleApiTestServer.init();
+            try {
+                base.evaluate();
+            } finally {
+                googleApiTestServer.reset();
+            }
+        }
+    }
+
+    public GoogleSheetsApiTestServer getGoogleApiTestServer() {
+        return googleApiTestServer;
+    }
+
+    public int getServerPort() {
+        return serverPort;
+    }
+}
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/AbstractGoogleSheetsStreamTestSupport.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/AbstractGoogleSheetsStreamTestSupport.java
index 4767a0c..1ca4f5e 100644
--- a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/AbstractGoogleSheetsStreamTestSupport.java
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/AbstractGoogleSheetsStreamTestSupport.java
@@ -16,8 +16,13 @@
  */
 package org.apache.camel.component.google.sheets.stream;
 
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.services.sheets.v4.Sheets;
 import org.apache.camel.CamelContext;
 import org.apache.camel.component.google.sheets.AbstractGoogleSheetsTestSupport;
+import org.apache.camel.component.google.sheets.BatchGoogleSheetsClientFactory;
+import org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServerRule;
 import org.apache.camel.util.IntrospectionSupport;
 
 /**
@@ -36,6 +41,18 @@ public class AbstractGoogleSheetsStreamTestSupport extends AbstractGoogleSheetsT
 
         // add GoogleSheetsComponent to Camel context
         final GoogleSheetsStreamComponent component = new GoogleSheetsStreamComponent(context);
+        component.setClientFactory(new BatchGoogleSheetsClientFactory(
+                new NetHttpTransport.Builder()
+                        .trustCertificatesFromJavaKeyStore(
+                                getClass().getResourceAsStream("/" + GoogleSheetsApiTestServerRule.SERVER_KEYSTORE),
+                                GoogleSheetsApiTestServerRule.SERVER_KEYSTORE_PASSWORD)
+                        .build(),
+                new JacksonFactory()) {
+            @Override
+            protected void configure(Sheets.Builder clientBuilder) {
+                clientBuilder.setRootUrl(String.format("https://localhost:%s/", googleSheetsApiTestServerRule.getServerPort()));
+            }
+        });
         component.setConfiguration(configuration);
         context.addComponent("google-sheets-stream", component);
 
diff --git a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/SheetsStreamConsumerIntegrationTest.java b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/SheetsStreamConsumerIntegrationTest.java
index 2aa7c5d..ea79f17 100644
--- a/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/SheetsStreamConsumerIntegrationTest.java
+++ b/components/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/SheetsStreamConsumerIntegrationTest.java
@@ -16,7 +16,9 @@
  */
 package org.apache.camel.component.google.sheets.stream;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.UUID;
 
 import com.google.api.services.sheets.v4.model.Spreadsheet;
 import com.google.api.services.sheets.v4.model.ValueRange;
@@ -26,6 +28,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.Assert;
 import org.junit.Test;
 
+import static org.apache.camel.component.google.sheets.server.GoogleSheetsApiTestServerAssert.assertThatGoogleApi;
 import static org.apache.camel.component.google.sheets.stream.GoogleSheetsStreamConstants.MAJOR_DIMENSION;
 import static org.apache.camel.component.google.sheets.stream.GoogleSheetsStreamConstants.RANGE;
 import static org.apache.camel.component.google.sheets.stream.GoogleSheetsStreamConstants.RANGE_INDEX;
@@ -34,14 +37,35 @@ import static org.apache.camel.component.google.sheets.stream.GoogleSheetsStream
 
 public class SheetsStreamConsumerIntegrationTest extends AbstractGoogleSheetsStreamTestSupport {
 
-    private String range = "A1:B2";
+    private String range = TEST_SHEET + "!A1:B2";
 
     @Test
     public void testConsumeValueRange() throws Exception {
-        Spreadsheet testSheet = getSpreadsheetWithTestData();
+        String spreadsheetId = UUID.randomUUID().toString();
 
-        context().addRoutes(createGoogleStreamRouteBuilder(testSheet.getSpreadsheetId()));
-        context().startRoute("google-stream-test");
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnSpreadsheet(spreadsheetId);
+
+        Spreadsheet testSheet = getSpreadsheet();
+
+        List<List<Object>> data = Arrays.asList(
+                Arrays.asList("a1", "b1"),
+                Arrays.asList("a2", "b2")
+        );
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .updateValuesRequest(spreadsheetId, range, data)
+                .andReturnUpdateResponse();
+
+        applyTestData(testSheet);
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .batchGetValuesRequest(testSheet.getSpreadsheetId(), range)
+                .andReturnValues(data);
+
+        context().addRoutes(createGoogleStreamRouteBuilder(testSheet.getSpreadsheetId(), false));
 
         MockEndpoint mock = getMockEndpoint("mock:result");
         mock.expectedMinimumMessageCount(1);
@@ -53,7 +77,7 @@ public class SheetsStreamConsumerIntegrationTest extends AbstractGoogleSheetsStr
         Assert.assertTrue(exchange.getIn().getHeaders().containsKey(RANGE_INDEX));
         Assert.assertTrue(exchange.getIn().getHeaders().containsKey(MAJOR_DIMENSION));
         Assert.assertEquals(testSheet.getSpreadsheetId(), exchange.getIn().getHeaders().get(SPREADSHEET_ID));
-        Assert.assertEquals(TEST_SHEET + "!" + range, exchange.getIn().getHeaders().get(RANGE));
+        Assert.assertEquals(range, exchange.getIn().getHeaders().get(RANGE));
         Assert.assertEquals(1, exchange.getIn().getHeaders().get(RANGE_INDEX));
         Assert.assertEquals("ROWS", exchange.getIn().getHeaders().get(MAJOR_DIMENSION));
 
@@ -66,13 +90,35 @@ public class SheetsStreamConsumerIntegrationTest extends AbstractGoogleSheetsStr
     }
 
     @Test
-    public void testConsumeRowValues() throws Exception {
-        Spreadsheet testSheet = getSpreadsheetWithTestData();
+    public void testConsumeValueRangeSplitResults() throws Exception {
+        String spreadsheetId = UUID.randomUUID().toString();
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .createSpreadsheetRequest()
+                .hasSheetTitle("TestData")
+                .andReturnSpreadsheet(spreadsheetId);
 
-        context().addRoutes(createGoogleStreamRouteBuilder(testSheet.getSpreadsheetId()));
-        context().startRoute("google-stream-values-test");
+        Spreadsheet testSheet = getSpreadsheet();
 
-        MockEndpoint mock = getMockEndpoint("mock:rows");
+        List<List<Object>> data = Arrays.asList(
+                Arrays.asList("a1", "b1"),
+                Arrays.asList("a2", "b2")
+        );
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .updateValuesRequest(spreadsheetId, range, data)
+                .andReturnUpdateResponse();
+
+        applyTestData(testSheet);
+
+        assertThatGoogleApi(getGoogleApiTestServer())
+                .batchGetValuesRequest(testSheet.getSpreadsheetId(), range)
+                .andReturnValues(data);
+
+        context().addRoutes(createGoogleStreamRouteBuilder(testSheet.getSpreadsheetId(), true));
+        context().startRoute("google-stream-test");
+
+        MockEndpoint mock = getMockEndpoint("mock:result");
         mock.expectedMinimumMessageCount(2);
         assertMockEndpointsSatisfied();
 
@@ -83,7 +129,7 @@ public class SheetsStreamConsumerIntegrationTest extends AbstractGoogleSheetsStr
         Assert.assertTrue(exchange.getIn().getHeaders().containsKey(VALUE_INDEX));
         Assert.assertTrue(exchange.getIn().getHeaders().containsKey(MAJOR_DIMENSION));
         Assert.assertEquals(testSheet.getSpreadsheetId(), exchange.getIn().getHeaders().get(SPREADSHEET_ID));
-        Assert.assertEquals(TEST_SHEET + "!" + range, exchange.getIn().getHeaders().get(RANGE));
+        Assert.assertEquals(range, exchange.getIn().getHeaders().get(RANGE));
         Assert.assertEquals(1, exchange.getIn().getHeaders().get(RANGE_INDEX));
         Assert.assertEquals(1, exchange.getIn().getHeaders().get(VALUE_INDEX));
         Assert.assertEquals("ROWS", exchange.getIn().getHeaders().get(MAJOR_DIMENSION));
@@ -109,14 +155,13 @@ public class SheetsStreamConsumerIntegrationTest extends AbstractGoogleSheetsStr
         Assert.assertEquals("b2", values.get(1));
     }
 
-    private RouteBuilder createGoogleStreamRouteBuilder(String spreadsheetId) throws Exception {
+    private RouteBuilder createGoogleStreamRouteBuilder(String spreadsheetId, boolean splitResults) throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() {
-                from("google-sheets-stream://data?spreadsheetId=" + spreadsheetId + "&range=" + range + "&delay=2000&maxResults=5").routeId("google-stream-test").to("mock:result");
-
-                from("google-sheets-stream://data?spreadsheetId=" + spreadsheetId 
-                    + "&range=" + range + "&delay=2000&maxResults=5&splitResults=true").routeId("google-stream-values-test").to("mock:rows");
+                from(String.format("google-sheets-stream://data?spreadsheetId=%s&range=%s&delay=20000&maxResults=5&splitResults=%s", spreadsheetId, range, splitResults))
+                        .routeId("google-stream-test")
+                        .to("mock:result");
             }
         };
     }
diff --git a/components/camel-google-sheets/src/test/resources/googleapis.jks b/components/camel-google-sheets/src/test/resources/googleapis.jks
new file mode 100644
index 0000000..0d6097c
Binary files /dev/null and b/components/camel-google-sheets/src/test/resources/googleapis.jks differ
diff --git a/components/camel-google-sheets/src/test/resources/test-options.properties b/components/camel-google-sheets/src/test/resources/test-options.properties
index b21908c..f2e4a6f 100644
--- a/components/camel-google-sheets/src/test/resources/test-options.properties
+++ b/components/camel-google-sheets/src/test/resources/test-options.properties
@@ -19,8 +19,8 @@
 ## Login properties for Google Sheets Component
 #####################################
 ## Application client id and secret
-clientId=
-clientSecret=
+clientId=syndesis-client
+clientSecret=syndesis
 applicationName=camel-google-sheets/1.0
-accessToken=
-refreshToken=
+accessToken=cd887efc-7c7d-4e8e-9580-f7502123badf
+refreshToken=bdbbe5ec-6081-4c6c-8974-9c4abfc0fdcc
diff --git a/parent/pom.xml b/parent/pom.xml
index 9d001b4..bd2138c 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -128,6 +128,7 @@
     <chronicle-wire-version>1.16.21</chronicle-wire-version>
     <chunk-templates-version>3.3.1</chunk-templates-version>
     <chunk-templates-bundle-version>3.3.1_1</chunk-templates-bundle-version>
+    <citrus.version>2.8.0</citrus.version>
     <cmis-version>1.1.0</cmis-version>
     <cometd-bayeux-version>6.1.11</cometd-bayeux-version>
     <cometd-java-client-version>3.1.2</cometd-java-client-version>
diff --git a/platforms/spring-boot/components-starter/camel-google-sheets-starter/src/main/java/org/apache/camel/component/google/sheets/stream/springboot/GoogleSheetsStreamComponentConfiguration.java b/platforms/spring-boot/components-starter/camel-google-sheets-starter/src/main/java/org/apache/camel/component/google/sheets/stream/springboot/GoogleSheetsStreamComponentConfiguration.java
index 8df3880..b02e6bb 100644
--- a/platforms/spring-boot/components-starter/camel-google-sheets-starter/src/main/java/org/apache/camel/component/google/sheets/stream/springboot/GoogleSheetsStreamComponentConfiguration.java
+++ b/platforms/spring-boot/components-starter/camel-google-sheets-starter/src/main/java/org/apache/camel/component/google/sheets/stream/springboot/GoogleSheetsStreamComponentConfiguration.java
@@ -127,7 +127,7 @@ public class GoogleSheetsStreamComponentConfiguration
          * number of rows in a returned value range data set or the number of
          * returned value ranges in a batch request.
          */
-        private Integer maxResults = 10;
+        private Integer maxResults = 0;
         /**
          * Specifies the range of rows and columns in a sheet to get data from.
          */