textReplacementRules, boolean doNotRecord) {
Objects.requireNonNull(testName, "'testName' cannot be null.");
Objects.requireNonNull(textReplacementRules, "'textReplacementRules' cannot be null.");
this.testName = testName;
this.testMode = TestMode.PLAYBACK;
+ this.allowedToReadRecordedValues = !doNotRecord;
+ this.allowedToRecordValues = false;
- this.recordedData = readDataFromFile();
+ this.recordedData = allowedToReadRecordedValues ? readDataFromFile() : null;
this.textReplacementRules = textReplacementRules;
}
@@ -120,7 +185,8 @@ public RecordedData getRecordedData() {
}
/**
- * Gets a new HTTP pipeline policy that records network calls and its data is managed by {@link InterceptorManager}.
+ * Gets a new HTTP pipeline policy that records network calls and its data is managed by {@link
+ * InterceptorManager}.
*
* @return HttpPipelinePolicy to record network calls.
*/
@@ -145,27 +211,24 @@ public HttpClient getPlaybackClient() {
*/
@Override
public void close() {
- switch (testMode) {
- case RECORD:
- try {
- writeDataToFile();
- } catch (IOException e) {
- logger.error("Unable to write data to playback file.", e);
- }
- break;
- case PLAYBACK:
- // Do nothing
- break;
- default:
- logger.error("==> Unknown AZURE_TEST_MODE: {}", testMode);
- break;
+ if (allowedToRecordValues) {
+ try {
+ writeDataToFile();
+ } catch (IOException e) {
+ logger.error("Unable to write data to playback file.", e);
+ }
}
}
- private RecordedData readDataFromFile() throws IOException {
+ private RecordedData readDataFromFile() {
File recordFile = getRecordFile(testName);
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
- return mapper.readValue(recordFile, RecordedData.class);
+
+ try {
+ return mapper.readValue(recordFile, RecordedData.class);
+ } catch (IOException ex) {
+ throw logger.logExceptionAsWarning(new UncheckedIOException(ex));
+ }
}
/*
@@ -183,10 +246,11 @@ private File getRecordFile(String testName) {
File playbackFile = new File(getRecordFolder(), testName + ".json");
if (!playbackFile.exists()) {
- throw logger.logExceptionAsError(new RuntimeException(String.format(
- "Missing playback file. File path: %s. ", playbackFile)));
+ throw logger.logExceptionAsError(new RuntimeException(String.format(
+ "Missing playback file. File path: %s. ", playbackFile.getPath())));
}
- logger.info("==> Playback file path: " + playbackFile);
+
+ logger.info("==> Playback file path: " + playbackFile.getPath());
return playbackFile;
}
@@ -218,7 +282,8 @@ private File createRecordFile(String testName) throws IOException {
}
/**
- * Add text replacement rule (regex as key, the replacement text as value) into {@link InterceptorManager#textReplacementRules}
+ * Add text replacement rule (regex as key, the replacement text as value) into {@link
+ * InterceptorManager#textReplacementRules}
*
* @param regex the pattern to locate the position of replacement
* @param replacement the replacement text
diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java
index e1d16afb4d100..a1294ddebbe42 100644
--- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java
+++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestBase.java
@@ -2,21 +2,21 @@
// Licensed under the MIT License.
package com.azure.core.test;
-import com.azure.core.util.Configuration;
import com.azure.core.test.utils.TestResourceNamer;
+import com.azure.core.util.Configuration;
import com.azure.core.util.logging.ClientLogger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.Locale;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
/**
* Base class for running live and playback tests using {@link InterceptorManager}.
*/
@@ -29,6 +29,8 @@ public abstract class TestBase implements BeforeEachCallback {
protected InterceptorManager interceptorManager;
protected TestResourceNamer testResourceNamer;
+ protected TestContextManager testContextManager;
+
private ExtensionContext extensionContext;
/**
@@ -53,16 +55,16 @@ public void beforeEach(ExtensionContext extensionContext) {
*/
@BeforeEach
public void setupTest(TestInfo testInfo) {
- final String testName = testInfo.getTestMethod().get().getName();
- logger.info("Test Mode: {}, Name: {}", testMode, testName);
+ this.testContextManager = new TestContextManager(testInfo.getTestMethod().get(), testMode);
+ logger.info("Test Mode: {}, Name: {}", testMode, testContextManager.getTestName());
try {
- interceptorManager = new InterceptorManager(testName, testMode);
- } catch (IOException e) {
- logger.error("Could not create interceptor for {}", testName, e);
+ interceptorManager = new InterceptorManager(testContextManager);
+ } catch (UncheckedIOException e) {
+ logger.error("Could not create interceptor for {}", testContextManager.getTestName(), e);
Assertions.fail();
}
- testResourceNamer = new TestResourceNamer(testName, testMode, interceptorManager.getRecordedData());
+ testResourceNamer = new TestResourceNamer(testContextManager, interceptorManager.getRecordedData());
beforeTest();
}
@@ -73,8 +75,10 @@ public void setupTest(TestInfo testInfo) {
*/
@AfterEach
public void teardownTest(TestInfo testInfo) {
- afterTest();
- interceptorManager.close();
+ if (testContextManager.didTestRun()) {
+ afterTest();
+ interceptorManager.close();
+ }
}
/**
@@ -133,4 +137,22 @@ private static TestMode initializeTestMode() {
logger.info("Environment variable '{}' has not been set yet. Using 'Playback' mode.", AZURE_TEST_MODE);
return TestMode.PLAYBACK;
}
+
+ /**
+ * Sleeps the test for the given amount of milliseconds if {@link TestMode} isn't {@link TestMode#PLAYBACK}.
+ *
+ * @param millis Number of milliseconds to sleep the test.
+ * @throws IllegalStateException If the sleep is interrupted.
+ */
+ protected void sleepIfRunningAgainstService(long millis) {
+ if (testMode == TestMode.PLAYBACK) {
+ return;
+ }
+
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException ex) {
+ throw logger.logExceptionAsWarning(new IllegalStateException(ex));
+ }
+ }
}
diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestContextManager.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestContextManager.java
new file mode 100644
index 0000000000000..6e7eb696b77c9
--- /dev/null
+++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestContextManager.java
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.core.test;
+
+import com.azure.core.test.annotation.DoNotRecord;
+
+import java.lang.reflect.Method;
+
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * This class handles managing context about a test, such as custom testing annotations and verifying whether the test
+ * is capable of running.
+ */
+public class TestContextManager {
+ private final String testName;
+ private final TestMode testMode;
+ private final boolean doNotRecord;
+ private final boolean skipInPlayback;
+ private final boolean testRan;
+
+ /**
+ * Constructs a {@link TestContextManager} based on the test method.
+ *
+ * @param testMethod Test method being ran.
+ * @param testMode The {@link TestMode} the test is running in.
+ */
+ public TestContextManager(Method testMethod, TestMode testMode) {
+ this.testName = testMethod.getName();
+ this.testMode = testMode;
+
+ DoNotRecord doNotRecordAnnotation = testMethod.getAnnotation(DoNotRecord.class);
+ if (doNotRecordAnnotation != null) {
+ this.doNotRecord = true;
+ this.skipInPlayback = doNotRecordAnnotation.skipInPlayback();
+ } else {
+ this.doNotRecord = false;
+ this.skipInPlayback = false;
+ }
+
+ this.testRan = !(skipInPlayback && testMode == TestMode.PLAYBACK);
+ assumeTrue(testRan, "Test does not allow playback and was ran in 'TestMode.PLAYBACK'");
+ }
+
+ /**
+ * Returns the name of the test being ran.
+ *
+ * @return The test name.
+ */
+ public String getTestName() {
+ return testName;
+ }
+
+ /**
+ * Returns the mode being used to run tests.
+ *
+ * @return The {@link TestMode} being used to run tests.
+ */
+ public TestMode getTestMode() {
+ return testMode;
+ }
+
+ /**
+ * Returns whether the test should have its network calls recorded during a {@link TestMode#RECORD record} test
+ * run.
+ *
+ * @return Flag indicating whether to record test network calls.
+ */
+ public boolean doNotRecordTest() {
+ return doNotRecord;
+ }
+
+ /**
+ * Returns whether the current test was ran.
+ *
+ * @return Flag indicating whether the current test was ran.
+ */
+ public boolean didTestRun() {
+ return testRan;
+ }
+}
diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java
index 27b1224ff19f6..5d89b4de093eb 100644
--- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java
+++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/TestMode.java
@@ -10,6 +10,10 @@ public enum TestMode {
* Record data from a live test.
*/
RECORD,
+ /**
+ * Run a live test without recording.
+ */
+ LIVE,
/**
* Playback data from an existing test session.
*/
diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/DoNotRecord.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/DoNotRecord.java
new file mode 100644
index 0000000000000..f2241c79de296
--- /dev/null
+++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/DoNotRecord.java
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.test.annotation;
+
+import com.azure.core.test.TestMode;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation given to some tests to indicate that network calls made during the test shouldn't be recorded.
+ *
+ *
+ * Pass {@code true} for {@link #skipInPlayback() skipInPlayback} to indicate that the test shouldn't run when tests are
+ * ran in {@link TestMode#PLAYBACK}. A common case for setting this to {@code true} is when the test has either
+ * sensitive content that cannot be redacted or calls into code that cannot be mocked.
+ */
+@Retention(RUNTIME)
+@Target({METHOD})
+public @interface DoNotRecord {
+
+ /**
+ * Returns whether the test will be ignored during a {@link TestMode#PLAYBACK playback} test run.
+ *
+ * @return Flag indicating if the test will be ignored during a playback test run.
+ */
+ boolean skipInPlayback() default false;
+}
diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/package-info.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/package-info.java
new file mode 100644
index 0000000000000..f114506b11efd
--- /dev/null
+++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/annotation/package-info.java
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+/**
+ * Package containing annotations test classes for Azure client libraries.
+ */
+package com.azure.core.test.annotation;
diff --git a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java
index 481d6be0607ba..3b38a1d2e0852 100644
--- a/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java
+++ b/sdk/core/azure-core-test/src/main/java/com/azure/core/test/utils/TestResourceNamer.java
@@ -3,12 +3,15 @@
package com.azure.core.test.utils;
+import com.azure.core.test.TestContextManager;
import com.azure.core.test.TestMode;
import com.azure.core.test.models.RecordedData;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
/**
* Provides random string names. If the test mode is {@link TestMode#PLAYBACK}, then names are fetched from
@@ -16,21 +19,48 @@
* persisted to {@link RecordedData}.
*/
public class TestResourceNamer extends ResourceNamer {
- private final TestMode testMode;
private final RecordedData recordedData;
+ private final boolean allowedToReadRecordedValues;
+ private final boolean allowedToRecordValues;
/**
* Constructor of TestResourceNamer
*
+ * @deprecated Use {@link #TestResourceNamer(TestContextManager, RecordedData)} instead.
* @param name test name as prefix
- * @param testMode the test mode {@link TestMode#PLAYBACK} or {@link TestMode#RECORD}
+ * @param testMode The {@link TestMode} which the test is running in.
* @param recordedData the recorded data with list of network call
*/
+ @Deprecated
public TestResourceNamer(String name, TestMode testMode, RecordedData recordedData) {
+ this(name, testMode, false, recordedData);
+ }
+
+ /**
+ * Constructor of TestResourceNamer
+ *
+ * @param testContextManager Contextual information about the test being ran, such as test name, {@link TestMode},
+ * and others.
+ * @param recordedData the recorded data with list of network call
+ * @throws NullPointerException If {@code testMode} isn't {@link TestMode#LIVE}, {@code doNotRecord} is
+ * {@code false}, and {@code recordedData} is {@code null}.
+ */
+ public TestResourceNamer(TestContextManager testContextManager, RecordedData recordedData) {
+ this(testContextManager.getTestName(), testContextManager.getTestMode(), testContextManager.doNotRecordTest(),
+ recordedData);
+ }
+
+ private TestResourceNamer(String name, TestMode testMode, boolean doNotRecord, RecordedData recordedData) {
super(name);
- Objects.requireNonNull(recordedData, "'recordedData' cannot be null.");
+
+ // Only need recordedData if the test is running in playback or record.
+ if (testMode != TestMode.LIVE && !doNotRecord) {
+ Objects.requireNonNull(recordedData, "'recordedData' cannot be null.");
+ }
+
this.recordedData = recordedData;
- this.testMode = testMode;
+ this.allowedToReadRecordedValues = (testMode == TestMode.PLAYBACK && !doNotRecord);
+ this.allowedToRecordValues = (testMode == TestMode.RECORD && !doNotRecord);
}
/**
@@ -42,13 +72,7 @@ public TestResourceNamer(String name, TestMode testMode, RecordedData recordedDa
*/
@Override
public String randomName(String prefix, int maxLen) {
- if (testMode == TestMode.PLAYBACK) {
- return recordedData.removeVariable();
- } else {
- String name = super.randomName(prefix, maxLen);
- recordedData.addVariable(name);
- return name;
- }
+ return getValue(readValue -> readValue, () -> super.randomName(prefix, maxLen));
}
/**
@@ -58,13 +82,7 @@ public String randomName(String prefix, int maxLen) {
*/
@Override
public String randomUuid() {
- if (testMode == TestMode.PLAYBACK) {
- return recordedData.removeVariable();
- } else {
- String uuid = super.randomUuid();
- recordedData.addVariable(uuid);
- return uuid;
- }
+ return getValue(readValue -> readValue, super::randomUuid);
}
/**
@@ -73,13 +91,7 @@ public String randomUuid() {
* @return OffsetDateTime of UTC now.
*/
public OffsetDateTime now() {
- if (testMode == TestMode.PLAYBACK) {
- return OffsetDateTime.parse(recordedData.removeVariable());
- } else {
- OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
- recordedData.addVariable(now.toString());
- return now;
- }
+ return getValue(OffsetDateTime::parse, () -> OffsetDateTime.now(ZoneOffset.UTC));
}
/**
@@ -89,10 +101,19 @@ public OffsetDateTime now() {
* @return the recorded value.
*/
public String recordValueFromConfig(String value) {
- if (testMode == TestMode.PLAYBACK) {
- return recordedData.removeVariable();
+ return getValue(readValue -> readValue, () -> value);
+ }
+
+ private T getValue(Function readHandler, Supplier valueSupplier) {
+ if (allowedToReadRecordedValues) {
+ return readHandler.apply(recordedData.removeVariable());
} else {
- recordedData.addVariable(value);
+ T value = valueSupplier.get();
+
+ if (allowedToRecordValues) {
+ recordedData.addVariable(value.toString());
+ }
+
return value;
}
}
diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/FakeTestClass.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/FakeTestClass.java
new file mode 100644
index 0000000000000..7dfe22900ffb5
--- /dev/null
+++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/FakeTestClass.java
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.core.test;
+
+import com.azure.core.test.annotation.DoNotRecord;
+import com.azure.core.util.logging.ClientLogger;
+
+import java.lang.reflect.Method;
+
+/**
+ * This is a dummy class used to mock scenarios for {@link TestContextManager}.
+ */
+public final class FakeTestClass {
+ public static final Method METHOD_WITHOUT_DONOTRECORD = getTestMethod("testWithoutDoNotRecord");
+ public static final Method DONOTRECORD_FALSE_SKIPINPLAYBACK = getTestMethod("testWithDoNotRecordRunInPlayback");
+ public static final Method DONOTRECORD_SKIPINPLAYBACK = getTestMethod("testWithDoNotRecordSkipInPlayback");
+
+ public void testWithoutDoNotRecord() {
+ }
+
+ @DoNotRecord
+ public void testWithDoNotRecordRunInPlayback() {
+
+ }
+
+ @DoNotRecord(skipInPlayback = true)
+ public void testWithDoNotRecordSkipInPlayback() {
+ }
+
+ private static Method getTestMethod(String methodName) {
+ try {
+ return FakeTestClass.class.getMethod(methodName);
+ } catch (NoSuchMethodException e) {
+ throw new ClientLogger(FakeTestClass.class).logExceptionAsWarning(new RuntimeException(e));
+ }
+ }
+}
diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/InterceptorManagerTests.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/InterceptorManagerTests.java
new file mode 100644
index 0000000000000..4d953e8a278eb
--- /dev/null
+++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/InterceptorManagerTests.java
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.test;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+
+import static com.azure.core.test.FakeTestClass.DONOTRECORD_FALSE_SKIPINPLAYBACK;
+import static com.azure.core.test.FakeTestClass.METHOD_WITHOUT_DONOTRECORD;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * Tests for {@link InterceptorManager}.
+ */
+public class InterceptorManagerTests {
+ /**
+ * Validates that {@link InterceptorManager#getRecordedData()} is {@code null} when testing in {@link
+ * TestMode#LIVE}.
+ */
+ @Test
+ public void recordedDataIsNullInLiveMode() {
+ assertNull(new InterceptorManager(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.LIVE))
+ .getRecordedData());
+ assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.LIVE))
+ .getRecordedData());
+ }
+
+ /**
+ * Validates that {@link InterceptorManager#getRecordedData()} is {@code null} when {@code doNotRecord} is passed as
+ * {@code true}.
+ */
+ @Test
+ public void recordedDataIsNullWhenDoNotRecord() {
+ assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.RECORD))
+ .getRecordedData());
+ assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.LIVE))
+ .getRecordedData());
+ assertNull(new InterceptorManager(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.PLAYBACK))
+ .getRecordedData());
+ assertNull(new InterceptorManager("testName", new HashMap<>(), true).getRecordedData());
+ }
+}
diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/TestContextManagerTests.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/TestContextManagerTests.java
new file mode 100644
index 0000000000000..b36654d276b31
--- /dev/null
+++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/TestContextManagerTests.java
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.test;
+
+import com.azure.core.test.annotation.DoNotRecord;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.opentest4j.TestAbortedException;
+
+import java.lang.reflect.Method;
+
+import static com.azure.core.test.FakeTestClass.DONOTRECORD_FALSE_SKIPINPLAYBACK;
+import static com.azure.core.test.FakeTestClass.DONOTRECORD_SKIPINPLAYBACK;
+import static com.azure.core.test.FakeTestClass.METHOD_WITHOUT_DONOTRECORD;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link TestContextManager}.
+ */
+public class TestContextManagerTests {
+
+ /**
+ * Validates that a test method without the {@link DoNotRecord} annotation is allowed to run in all test modes and
+ * will record network calls and test values.
+ */
+ @ParameterizedTest(name = "[{index}] {displayName}")
+ @EnumSource(TestMode.class)
+ public void testWithoutDoNotRecord(TestMode testMode) {
+ TestContextManager testContextManager = new TestContextManager(METHOD_WITHOUT_DONOTRECORD, testMode);
+
+ assertFalse(testContextManager.doNotRecordTest());
+ assertTrue(testContextManager.didTestRun());
+ }
+
+ /**
+ * Validates that a test method with the default {@link DoNotRecord} annotation is allowed to run in all test modes
+ * but doesn't have its network calls or test values recorded.
+ */
+ @ParameterizedTest(name = "[{index}] {displayName}")
+ @EnumSource(TestMode.class)
+ public void testWithDoNotRecordRunInPlayback(TestMode testMode) {
+ TestContextManager testContextManager = new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, testMode);
+
+ assertTrue(testContextManager.doNotRecordTest());
+ assertTrue(testContextManager.didTestRun());
+ }
+
+ /**
+ * Validates that a test method with the {@link DoNotRecord} annotation having {@code skipInPlayback} set to true is
+ * only allowed to run in {@link TestMode#RECORD} and {@link TestMode#LIVE} and won't have its network calls or test
+ * values recorded.
+ */
+ @Test
+ public void testWithDoNotRecordSkipInPlayback() {
+ Method testMethod = DONOTRECORD_SKIPINPLAYBACK;
+
+ assertThrows(TestAbortedException.class, () -> new TestContextManager(testMethod, TestMode.PLAYBACK));
+
+ TestContextManager testContextManager = new TestContextManager(testMethod, TestMode.LIVE);
+ assertTrue(testContextManager.doNotRecordTest());
+ assertTrue(testContextManager.didTestRun());
+
+ testContextManager = new TestContextManager(testMethod, TestMode.RECORD);
+ assertTrue(testContextManager.doNotRecordTest());
+ assertTrue(testContextManager.didTestRun());
+ }
+}
diff --git a/sdk/core/azure-core-test/src/test/java/com/azure/core/test/utils/TestResourceNamerTests.java b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/utils/TestResourceNamerTests.java
new file mode 100644
index 0000000000000..94046262148fe
--- /dev/null
+++ b/sdk/core/azure-core-test/src/test/java/com/azure/core/test/utils/TestResourceNamerTests.java
@@ -0,0 +1,113 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.test.utils;
+
+import com.azure.core.test.TestContextManager;
+import com.azure.core.test.TestMode;
+import com.azure.core.test.models.RecordedData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+import java.util.NoSuchElementException;
+
+import static com.azure.core.test.FakeTestClass.DONOTRECORD_FALSE_SKIPINPLAYBACK;
+import static com.azure.core.test.FakeTestClass.METHOD_WITHOUT_DONOTRECORD;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for {@link TestResourceNamer}.
+ */
+public class TestResourceNamerTests {
+ private static final String A_VARIABLE = "aVariable";
+ private static final String RANDOM_NAME_PREFIX = "prefix";
+ private static final int RANDOM_NAME_LENGTH = 12;
+ private static final String CONFIG_VALUE = "value";
+
+ /**
+ * Validates that a {@link NullPointerException} is thrown if {@code testMode} isn't {@link TestMode#LIVE} and
+ * {@code doNotRecord} is {@code false}, otherwise no exception is thrown as having no {@link RecordedData} is
+ * valid in those cases.
+ */
+ @Test
+ public void nullRecordedData() {
+ // Doesn't throw when TestMode.LIVE.
+ assertDoesNotThrow(() ->
+ new TestResourceNamer(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.LIVE), null));
+
+ // Doesn't throw when 'doNotRecord' is true.
+ assertDoesNotThrow(() ->
+ new TestResourceNamer(new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.RECORD), null));
+
+ // Does throw when TestMode isn't LIVE and doNotRecord = false
+ assertThrows(NullPointerException.class, () ->
+ new TestResourceNamer(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.RECORD), null));
+ assertThrows(NullPointerException.class, () ->
+ new TestResourceNamer(new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.PLAYBACK), null));
+ }
+
+ /**
+ * Validates that when {@code doNotRecord} is {@code true} and {@code testMode} is {@link TestMode#PLAYBACK} then
+ * the {@link RecordedData} within the {@link TestResourceNamer} cannot be read.
+ */
+ @Test
+ public void recordedDataIsNotAllowedToReadRecordedValues() {
+ TestResourceNamer resourceNamer = new TestResourceNamer(new TestContextManager(
+ DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.PLAYBACK), getRecordedDataWithValue());
+
+ assertNotEquals(A_VARIABLE, resourceNamer.randomName("prefix", 12));
+ assertNotEquals(A_VARIABLE, resourceNamer.randomUuid());
+ assertNotEquals(A_VARIABLE, resourceNamer.now());
+ assertEquals(A_VARIABLE, resourceNamer.recordValueFromConfig(A_VARIABLE));
+ }
+
+ /**
+ * Validates that when {@code testMode} is {@link TestMode#LIVE} or {@code doNotRecord} is {@code true} then the
+ * {@link RecordedData} within the {@link TestResourceNamer} cannot record values generated.
+ */
+ @Test
+ public void recordedDataIsNotAllowedToRecordValues() {
+ RecordedData recordedData = new RecordedData();
+
+ callNamerMethds(new TestResourceNamer(
+ new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.LIVE), recordedData));
+ validateNoRecordingsMade(new TestResourceNamer(
+ new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.PLAYBACK), recordedData));
+
+ // Reset the recording data.
+ recordedData = new RecordedData();
+
+ callNamerMethds(new TestResourceNamer(
+ new TestContextManager(DONOTRECORD_FALSE_SKIPINPLAYBACK, TestMode.RECORD), recordedData));
+ validateNoRecordingsMade(new TestResourceNamer(
+ new TestContextManager(METHOD_WITHOUT_DONOTRECORD, TestMode.PLAYBACK), recordedData));
+ }
+
+ private void callNamerMethds(TestResourceNamer resourceNamer) {
+ resourceNamer.randomName(RANDOM_NAME_PREFIX, RANDOM_NAME_LENGTH);
+ resourceNamer.randomUuid();
+ resourceNamer.now();
+ resourceNamer.recordValueFromConfig(CONFIG_VALUE);
+ }
+
+ private void validateNoRecordingsMade(TestResourceNamer resourceNamer) {
+ assertNoSuchElementException(() -> resourceNamer.randomName(RANDOM_NAME_PREFIX, RANDOM_NAME_LENGTH));
+ assertNoSuchElementException(resourceNamer::randomUuid);
+ assertNoSuchElementException(resourceNamer::now);
+ assertNoSuchElementException(() -> resourceNamer.recordValueFromConfig(CONFIG_VALUE));
+ }
+
+ private void assertNoSuchElementException(Executable executable) {
+ assertThrows(NoSuchElementException.class, executable, "Expected 'NoSuchElementException' to be thrown.");
+ }
+
+ private RecordedData getRecordedDataWithValue() {
+ RecordedData recordedData = new RecordedData();
+ recordedData.addVariable(A_VARIABLE);
+
+ return recordedData;
+ }
+}