Repository: googleapis/java-storage-nio
Pull request #777 (2021-12-16 14:27:26)

test(deps): update dependency org.mockito:mockito-core to v4.2.0
[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [org.mockito:mockito-core](https://togithub.com/mockito/mockito) | `4.1.0` -> `4.2.0` | [![age](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.2.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.2.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.2.0/compatibility-slim/4.1.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/org.mockito:mockito-core/4.2.0/confidence-slim/4.1.0)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>mockito/mockito</summary>

### [`v4.2.0`](https://togithub.com/mockito/mockito/releases/v4.2.0)

[Compare Source](https://togithub.com/mockito/mockito/compare/v4.1.0...v4.2.0)

<sup><sup>*Changelog generated by [Shipkit Changelog Gradle Plugin](https://togithub.com/shipkit/shipkit-changelog)*</sup></sup>

##### 4.2.0

-   2021-12-16 - [21 commit(s)](https://togithub.com/mockito/mockito/compare/v4.1.0...v4.2.0) by Liam Miller-Cushon, Rafael Winterhalter, Tim van der Lippe, dependabot\[bot], temp-droid
-   Update ByteBuddy to 1.12.4 [(#&#8203;2515)](https://togithub.com/mockito/mockito/pull/2515)
-   Bump kotlinVersion from 1.6.0 to 1.6.10 [(#&#8203;2514)](https://togithub.com/mockito/mockito/pull/2514)
-   Add DoNotMock mention to main JavaDoc [(#&#8203;2512)](https://togithub.com/mockito/mockito/pull/2512)
-   Replace ExpectedException in MissingInvocationInOrderCheckerTest [(#&#8203;2511)](https://togithub.com/mockito/mockito/pull/2511)
-   Update Gradle to version 7.3.1 [(#&#8203;2509)](https://togithub.com/mockito/mockito/pull/2509)
-   Fixes [#&#8203;2497](https://togithub.com/mockito/mockito/issues/2497): Throw exception on invalid matchers for `mockStatic` [(#&#8203;2506)](https://togithub.com/mockito/mockito/pull/2506)
-   Make sure interface types are initialized before inline mocking to avoid blocking code of static initializers. [(#&#8203;2505)](https://togithub.com/mockito/mockito/pull/2505)
-   Bump org.eclipse.osgi from 3.17.0 to 3.17.100 [(#&#8203;2504)](https://togithub.com/mockito/mockito/pull/2504)
-   Bump com.diffplug.spotless from 6.0.2 to 6.0.4 [(#&#8203;2501)](https://togithub.com/mockito/mockito/pull/2501)
-   Bump com.diffplug.spotless from 6.0.1 to 6.0.2 [(#&#8203;2498)](https://togithub.com/mockito/mockito/pull/2498)
-   ArgumentMatchers not working for Mockito.mockStatic [(#&#8203;2497)](https://togithub.com/mockito/mockito/issues/2497)
-   Bump versions.bytebuddy from 1.12.2 to 1.12.3 [(#&#8203;2496)](https://togithub.com/mockito/mockito/pull/2496)
-   Bump com.diffplug.spotless from 6.0.0 to 6.0.1 [(#&#8203;2495)](https://togithub.com/mockito/mockito/pull/2495)
-   Remove the recommendation to import ArgumentMatchers methods using Mockito [(#&#8203;2494)](https://togithub.com/mockito/mockito/pull/2494)
-   Bump versions.junitJupiter from 5.8.1 to 5.8.2 [(#&#8203;2491)](https://togithub.com/mockito/mockito/pull/2491)
-   Bump junit-platform-launcher from 1.8.1 to 1.8.2 [(#&#8203;2490)](https://togithub.com/mockito/mockito/pull/2490)
-   Fix typo 'DoNoMock' should be 'DoNotMock' [(#&#8203;2487)](https://togithub.com/mockito/mockito/pull/2487)
-   Bump biz.aQute.bnd.gradle from 6.0.0 to 6.1.0 [(#&#8203;2486)](https://togithub.com/mockito/mockito/pull/2486)
-   Bump biz.aQute.bnd.builder from 6.0.0 to 6.1.0 [(#&#8203;2485)](https://togithub.com/mockito/mockito/pull/2485)
-   Bump versions.bytebuddy from 1.12.1 to 1.12.2 [(#&#8203;2484)](https://togithub.com/mockito/mockito/pull/2484)
-   Bump google-java-format from 1.12.0 to 1.13.0 [(#&#8203;2483)](https://togithub.com/mockito/mockito/pull/2483)
-   Add annotation to mark a type as DoNotMock [(#&#8203;1833)](https://togithub.com/mockito/mockito/pull/1833)

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox.

---

This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-storage-nio).


Patch:
diff --git a/google-cloud-nio/pom.xml b/google-cloud-nio/pom.xml
index 7025b46..b6e2256 100644
--- a/google-cloud-nio/pom.xml
+++ b/google-cloud-nio/pom.xml
@@ -80,7 +80,7 @@
     <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
-      <version>4.1.0</version>
+      <version>4.2.0</version>
       <scope>test</scope>
     </dependency><dependency>
       <groupId>org.hamcrest</groupId>
diff --git a/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java
index 4f2953b..b488ceb 100644
--- a/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java
+++ b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java
@@ -93,7 +93,7 @@ public final class CloudStorageFileSystemProvider extends FileSystemProvider {
   private final @Nullable String userProject;
 
   // used only when we create a new instance of CloudStorageFileSystemProvider.
-  private static StorageOptions futureStorageOptions;
+  private static StorageOptions futureStorageOptions = StorageOptionsUtil.getDefaultInstance();
 
   private static class LazyPathIterator extends AbstractIterator<Path> {
     private final Iterator<Blob> blobIterator;
@@ -198,20 +198,18 @@ public final class CloudStorageFileSystemProvider extends FileSystemProvider {
    */
   CloudStorageFileSystemProvider(
       @Nullable String userProject, @Nullable StorageOptions gcsStorageOptions) {
-    this.storageOptions = gcsStorageOptions;
+    this.storageOptions =
+        gcsStorageOptions != null
+            ? StorageOptionsUtil.mergeOptionsWithUserAgent(gcsStorageOptions)
+            : StorageOptionsUtil.getDefaultInstance();
     this.userProject = userProject;
   }
 
   // Initialize this.storage, once. This may throw an exception if default authentication
   // credentials are not available (hence not doing it in the ctor).
   private void initStorage() {
-    if (this.storage != null) {
-      return;
-    }
-    if (storageOptions == null) {
-      this.storage = StorageOptions.getDefaultInstance().getService();
-    } else {
-      this.storage = storageOptions.getService();
+    if (this.storage == null) {
+      doInitStorage();
     }
   }
 
@@ -1037,4 +1035,9 @@ public final class CloudStorageFileSystemProvider extends FileSystemProvider {
     }
     return new IOException(oops.getMessage(), oops);
   }
+
+  @VisibleForTesting
+  void doInitStorage() {
+    this.storage = storageOptions.getService();
+  }
 }
diff --git a/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtil.java b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtil.java
new file mode 100644
index 0000000..27c28bf
--- /dev/null
+++ b/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtil.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed 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 com.google.cloud.storage.contrib.nio;
+
+import com.google.api.gax.rpc.FixedHeaderProvider;
+import com.google.api.gax.rpc.HeaderProvider;
+import com.google.cloud.storage.StorageOptions;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+final class StorageOptionsUtil {
+  static final String USER_AGENT_ENTRY_NAME = "gcloud-java-nio";
+  static final String USER_AGENT_ENTRY_VERSION = getVersion();
+  private static final String USER_AGENT_ENTRY =
+      String.format("%s/%s", USER_AGENT_ENTRY_NAME, USER_AGENT_ENTRY_VERSION);
+  private static final FixedHeaderProvider DEFAULT_HEADER_PROVIDER =
+      FixedHeaderProvider.create("user-agent", USER_AGENT_ENTRY);
+
+  private static final StorageOptions DEFAULT_STORAGE_OPTIONS_INSTANCE =
+      StorageOptions.newBuilder().setHeaderProvider(DEFAULT_HEADER_PROVIDER).build();
+  private static final FixedHeaderProvider EMTPY_HEADER_PROVIDER =
+      FixedHeaderProvider.create(Collections.emptyMap());
+
+  private StorageOptionsUtil() {}
+
+  static StorageOptions getDefaultInstance() {
+    return DEFAULT_STORAGE_OPTIONS_INSTANCE;
+  }
+
+  static StorageOptions mergeOptionsWithUserAgent(StorageOptions providedStorageOptions) {
+    if (providedStorageOptions == DEFAULT_STORAGE_OPTIONS_INSTANCE) {
+      return providedStorageOptions;
+    }
+
+    String userAgent = providedStorageOptions.getUserAgent();
+    if (userAgent == null) {
+      return nullSafeSet(providedStorageOptions, DEFAULT_HEADER_PROVIDER);
+    } else {
+      if (!userAgent.contains(USER_AGENT_ENTRY_NAME)) {
+        HeaderProvider providedHeaderProvider = getHeaderProvider(providedStorageOptions);
+        Map<String, String> newHeaders = new HashMap<>(providedHeaderProvider.getHeaders());
+        newHeaders.put("user-agent", String.format("%s %s", userAgent, USER_AGENT_ENTRY));
+        FixedHeaderProvider headerProvider =
+            FixedHeaderProvider.create(ImmutableMap.copyOf(newHeaders));
+        return nullSafeSet(providedStorageOptions, headerProvider);
+      } else {
+        return providedStorageOptions;
+      }
+    }
+  }
+
+  /**
+   * Due to some complex interactions between init and mocking, it's possible that the builder
+   * instance returned from {@link StorageOptions#toBuilder()} can be null. This utility method will
+   * attempt to create the builder and set the new header provider. If however the builder instance
+   * is null, the orignal options will be returned without setting the header provider.
+   *
+   * <p>Since this method is only every called by us trying to add our user-agent entry to the
+   * headers this makes our attempt effectively a no-op, which is much better than failing customer
+   * code.
+   */
+  private static StorageOptions nullSafeSet(
+      StorageOptions storageOptions, HeaderProvider headerProvider) {
+    StorageOptions.Builder builder = storageOptions.toBuilder();
+    if (builder == null) {
+      return storageOptions;
+    } else {
+      return builder.setHeaderProvider(headerProvider).build();
+    }
+  }
+
+  /** Resolve the version of google-cloud-nio for inclusion in request meta-data */
+  private static String getVersion() {
+    // attempt to read the library's version from a properties file generated during the build
+    // this value should be read and cached for later use
+    String version = "";
+    try (InputStream inputStream =
+        CloudStorageFileSystemProvider.class.getResourceAsStream(
+            "/META-INF/maven/com.google.cloud/google-cloud-nio/pom.properties")) {
+      if (inputStream != null) {
+        final Properties properties = new Properties();
+        properties.load(inputStream);
+        version = properties.getProperty("version");
+      }
+    } catch (IOException e) {
+      // ignore
+    }
+    return version;
+  }
+
+  /**
+   * {@link com.google.cloud.ServiceOptions} does not specify a getter for the headerProvider, so
+   * instead merge with an empty provider.
+   */
+  @VisibleForTesting
+  static HeaderProvider getHeaderProvider(StorageOptions options) {
+    return options.getMergedHeaderProvider(EMTPY_HEADER_PROVIDER);
+  }
+}


Test patch:
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java
index 5fb9154..fc673c0 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributeViewTest.java
@@ -30,6 +30,7 @@ import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.attribute.FileTime;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -52,6 +53,11 @@ public class CloudStorageFileAttributeViewTest {
     path = Paths.get(URI.create("gs://red/water"));
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testReadAttributes() throws IOException {
     Files.write(path, HAPPY, CloudStorageOptions.withCacheControl("potato"));
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java
index 9f0914d..92efba8 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileAttributesTest.java
@@ -29,6 +29,7 @@ import java.net.URI;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -53,6 +54,11 @@ public class CloudStorageFileAttributesTest {
     dir = Paths.get(URI.create("gs://bucket/randompath/"));
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testCacheControl() throws IOException {
     Files.write(path, HAPPY, CloudStorageOptions.withCacheControl("potato"));
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java
index 2b90173..9f264ea 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java
@@ -26,6 +26,7 @@ import static java.nio.file.StandardOpenOption.CREATE;
 import static java.nio.file.StandardOpenOption.CREATE_NEW;
 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
 import static java.nio.file.StandardOpenOption.WRITE;
+import static org.junit.Assert.assertTrue;
 
 import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
 import com.google.cloud.testing.junit4.MultipleAttemptsRule;
@@ -52,6 +53,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -99,6 +101,11 @@ public class CloudStorageFileSystemProviderTest {
     CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions());
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testSize() throws Exception {
     Path path = Paths.get(URI.create("gs://bucket/wat"));
@@ -795,6 +802,21 @@ public class CloudStorageFileSystemProviderTest {
     assertThat(path4.toString()).isEqualTo("/with/a%20percent");
   }
 
+  @Test
+  public void testVersion_matchesAcceptablePatterns() {
+    String acceptableVersionPattern = "|(?:\\d+\\.\\d+\\.\\d+(?:-.*?)?(?:-SNAPSHOT)?)";
+    String version = StorageOptionsUtil.USER_AGENT_ENTRY_VERSION;
+    assertTrue(
+        String.format("the loaded version '%s' did not match the acceptable pattern", version),
+        version.matches(acceptableVersionPattern));
+  }
+
+  @Test
+  public void getUserAgentStartsWithCorrectToken() {
+    assertThat(String.format("gcloud-java-nio/%s", StorageOptionsUtil.USER_AGENT_ENTRY_VERSION))
+        .startsWith("gcloud-java-nio/");
+  }
+
   private static CloudStorageConfiguration permitEmptyPathComponents(boolean value) {
     return CloudStorageConfiguration.builder().permitEmptyPathComponents(value).build();
   }
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java
index 2819849..9914e8e 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java
@@ -46,6 +46,7 @@ import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.List;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -73,6 +74,11 @@ public class CloudStorageFileSystemTest {
     CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions());
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void checkDefaultOptions() throws IOException {
     // 1. We get the normal default if we don't do anything special.
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java
index 16647fb..e78d685 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageIsDirectoryTest.java
@@ -29,6 +29,7 @@ import com.google.cloud.storage.StorageOptions;
 import com.google.cloud.testing.junit4.MultipleAttemptsRule;
 import com.google.common.collect.Lists;
 import java.nio.file.Files;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -57,6 +58,11 @@ public class CloudStorageIsDirectoryTest {
     CloudStorageFileSystemProvider.setStorageOptions(mockOptions);
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testIsDirectoryNoUserProject() {
     CloudStorageFileSystem fs =
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java
index 8142a31..f0cecea 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageLateInitializationTest.java
@@ -16,56 +16,41 @@
 
 package com.google.cloud.storage.contrib.nio;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.google.cloud.storage.Storage;
-import com.google.cloud.storage.StorageOptions;
 import com.google.cloud.testing.junit4.MultipleAttemptsRule;
 import java.net.URI;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
 
 /** Unit tests for {@link CloudStorageFileSystemProvider} late initialization. */
-@RunWith(JUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
 public class CloudStorageLateInitializationTest {
   @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3);
 
-  private StorageOptions mockOptions;
-
-  @Before
-  public void before() {
-    mockOptions = mock(StorageOptions.class);
-    Storage mockStorage = mock(Storage.class);
-    when(mockOptions.getService()).thenReturn(mockStorage);
-    CloudStorageFileSystemProvider.setStorageOptions(mockOptions);
-  }
+  @Spy private final CloudStorageFileSystemProvider provider = new CloudStorageFileSystemProvider();
 
   @Test
   public void ctorDoesNotCreateStorage() {
-    new CloudStorageFileSystemProvider();
-    verify(mockOptions, never()).getService();
+    verify(provider, never()).doInitStorage();
   }
 
   @Test
   public void getPathCreatesStorageOnce() {
-    CloudStorageFileSystemProvider provider = new CloudStorageFileSystemProvider();
     provider.getPath(URI.create("gs://bucket1/wat"));
     provider.getPath(URI.create("gs://bucket2/wat"));
-    verify(mockOptions, times(1)).getService();
+    verify(provider, times(1)).doInitStorage();
   }
 
   @Test
   public void getFileSystemCreatesStorageOnce() {
-    CloudStorageFileSystemProvider provider = new CloudStorageFileSystemProvider();
     provider.getFileSystem(URI.create("gs://bucket1"));
     provider.getFileSystem(URI.create("gs://bucket2"));
-    verify(mockOptions, times(1)).getService();
+    verify(provider, times(1)).doInitStorage();
   }
 }
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java
index adda343..f541e90 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageOptionsTest.java
@@ -28,6 +28,7 @@ import java.net.URI;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -44,6 +45,11 @@ public class CloudStorageOptionsTest {
     CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions());
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testWithoutCaching() throws IOException {
     Path path = Paths.get(URI.create("gs://bucket/path"));
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java
index 107569c..2a2d47b 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStoragePathTest.java
@@ -30,6 +30,7 @@ import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
 import java.nio.file.ProviderMismatchException;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -47,6 +48,11 @@ public class CloudStoragePathTest {
     CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions());
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testCreate_neverRemoveExtraSlashes() throws IOException {
     try (CloudStorageFileSystem fs = CloudStorageFileSystem.forBucket("doodle")) {
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java
index e6e9748..a202cc3 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageReadTest.java
@@ -32,6 +32,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.Arrays;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -61,6 +62,11 @@ public class CloudStorageReadTest {
     CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions());
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   @Test
   public void testInputStreamReads() throws IOException, InterruptedException {
     // fill in the file
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java
index fdab9d7..2c34bc0 100644
--- a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/NIOTest.java
@@ -23,6 +23,7 @@ import com.google.cloud.testing.junit4.MultipleAttemptsRule;
 import java.net.URI;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -45,6 +46,11 @@ public class NIOTest {
     uriToCloudStorage = URI.create("gs://bucket/file.txt");
   }
 
+  @After
+  public void after() {
+    CloudStorageFileSystemProvider.setStorageOptions(StorageOptionsUtil.getDefaultInstance());
+  }
+
   /** We can create a Path object from a gs:// URI. * */
   @Test
   public void testCreatePath() {
diff --git a/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtilTest.java b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtilTest.java
new file mode 100644
index 0000000..8dc9383
--- /dev/null
+++ b/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/StorageOptionsUtilTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed 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 com.google.cloud.storage.contrib.nio;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.gax.rpc.FixedHeaderProvider;
+import com.google.api.gax.rpc.HeaderProvider;
+import com.google.cloud.NoCredentials;
+import com.google.cloud.storage.StorageOptions;
+import java.util.Map;
+import org.junit.Test;
+
+public final class StorageOptionsUtilTest {
+
+  @Test
+  public void ensureDefaultInstanceCarriedThrough() {
+    StorageOptions defaultInstance = StorageOptionsUtil.getDefaultInstance();
+    StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(defaultInstance);
+
+    assertSame(defaultInstance, merged);
+  }
+
+  @Test
+  public void ensureUserAgentEntryAddedIfUserAgentSpecified() {
+    StorageOptions defaultInstance =
+        defaults()
+            .toBuilder()
+            .setHeaderProvider(FixedHeaderProvider.create("user-agent", "asdf/ gcloud-java-nio/"))
+            .build();
+    StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(defaultInstance);
+
+    String mergedUserAgent = merged.getUserAgent();
+    assertNotNull(mergedUserAgent);
+    assertTrue(mergedUserAgent.contains("gcloud-java-nio/"));
+  }
+
+  @Test
+  public void ensureUserAgentEntryLeftInTactIfAlreadySpecifiesOurEntry() {
+    StorageOptions defaultInstance = defaults();
+    StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(defaultInstance);
+
+    String mergedUserAgent = merged.getUserAgent();
+    assertNotNull(mergedUserAgent);
+    assertTrue(mergedUserAgent.contains("gcloud-java-nio/"));
+  }
+
+  @Test
+  public void ensureAddingUserAgentEntryDoesNotClobberAnyProvidedHeaders() {
+    StorageOptions base =
+        defaults()
+            .toBuilder()
+            .setHeaderProvider(
+                FixedHeaderProvider.create(
+                    "user-agent", "custom/",
+                    "x-custom-header", "value"))
+            .build();
+    StorageOptions merged = StorageOptionsUtil.mergeOptionsWithUserAgent(base);
+
+    String mergedUserAgent = merged.getUserAgent();
+
+    HeaderProvider mergedHeaderProvider = StorageOptionsUtil.getHeaderProvider(merged);
+    Map<String, String> headers = mergedHeaderProvider.getHeaders();
+    String customHeader = headers.get("x-custom-header");
+
+    assertNotNull(mergedUserAgent);
+    assertNotNull(customHeader);
+    assertEquals("value", customHeader);
+    assertTrue(mergedUserAgent.contains("custom/ gcloud-java-nio/"));
+  }
+
+  private StorageOptions defaults() {
+    return StorageOptions.newBuilder()
+        .setCredentials(NoCredentials.getInstance())
+        .setProjectId("proj")
+        .build();
+  }
+}
diff --git a/google-cloud-nio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/google-cloud-nio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d
--- /dev/null
+++ b/google-cloud-nio/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1,1 @@
+mock-maker-inline