Overhaul TestExecutionListener for Micrometer ObservationRegistry
This commit overhauls the TestExecutionListener for Micrometer's ObservationRegistry that was introduced in the previous commit. Specifically, this commit: - Renames the listener to MicrometerObservationRegistryTestExecutionListener since the use of a ThreadLocal is an implementation detail that may change over time. - Makes the listener package-private instead of public in order to allow the team greater flexibility in evolving this feature. - Eagerly loads the ObservationThreadLocalAccessor class and verifies that it has a getObservationRegistry() method to ensure that the listener is properly skipped when SpringFactoriesLoader attempts to load it, if Micrometer 1.10.8+ is not on the classpath. - Switches the listener's automatic registration order to 2500 in order to register it after the DependencyInjectionTestExecutionListener. - Only tracks the previous ObservationRegistry in beforeTestMethod() if the test's ApplicationContext contains an ObservationRegistry bean. - Properly removes the TestContext attribute for the previous ObservationRegistry in afterTestMethod(). - Introduces DEBUG logging for diagnostics. - Adds an entry in the Javadoc for TestExecutionListener as well as in the Testing chapter in the reference manual. Closes gh-30658
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,13 +28,13 @@ import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.test.context.event.ApplicationEventsTestExecutionListener;
|
||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
||||
import org.springframework.test.context.observation.MicrometerObservationThreadLocalTestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
@@ -57,14 +57,17 @@ import static org.springframework.test.context.TestExecutionListeners.MergeMode.
|
||||
*/
|
||||
class TestExecutionListenersTests {
|
||||
|
||||
private static final Class<?> micrometerListenerClass =
|
||||
ClassUtils.resolveClassName("org.springframework.test.context.observation.MicrometerObservationRegistryTestExecutionListener", null);
|
||||
|
||||
@Test
|
||||
void defaultListeners() {
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,//
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
micrometerListenerClass,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,//
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class
|
||||
@@ -82,8 +85,8 @@ class TestExecutionListenersTests {
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
micrometerListenerClass,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,//
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class
|
||||
@@ -100,8 +103,8 @@ class TestExecutionListenersTests {
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
micrometerListenerClass,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class,//
|
||||
@@ -120,8 +123,8 @@ class TestExecutionListenersTests {
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
BarTestExecutionListener.class,//
|
||||
micrometerListenerClass,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,//
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class
|
||||
@@ -366,9 +369,9 @@ class TestExecutionListenersTests {
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// 2500 is between DependencyInjectionTestExecutionListener (2000) and
|
||||
// DirtiesContextTestExecutionListener (3000)
|
||||
return 2500;
|
||||
// 2250 is between DependencyInjectionTestExecutionListener (2000) and
|
||||
// MicrometerObservationRegistryTestExecutionListener (2500)
|
||||
return 2250;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.test.context.observation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MicrometerObservationRegistryTestExecutionListener}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Sam Brannen
|
||||
* @since 6.0.10
|
||||
*/
|
||||
class MicrometerObservationRegistryTestExecutionListenerTests {
|
||||
|
||||
private final ObservationRegistry originalObservationRegistry = globalObservationRegistry();
|
||||
|
||||
private final TestContext testContext = mock();
|
||||
|
||||
private final StaticApplicationContext applicationContext = new StaticApplicationContext();
|
||||
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
private final TestExecutionListener listener = new MicrometerObservationRegistryTestExecutionListener();
|
||||
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" }) // for raw Class testClass
|
||||
void configureTestContextMock() {
|
||||
willAnswer(invocation -> attributes.put(invocation.getArgument(0), invocation.getArgument(1)))
|
||||
.given(testContext).setAttribute(anyString(), any());
|
||||
given(testContext.removeAttribute(anyString()))
|
||||
.willAnswer(invocation -> attributes.get(invocation.getArgument(0, String.class)));
|
||||
given(testContext.getApplicationContext()).willReturn(applicationContext);
|
||||
Class testClass = getClass();
|
||||
given(testContext.getTestClass()).willReturn(testClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
void observationRegistryIsNotOverridden() throws Exception {
|
||||
assertGlobalObservationRegistryIsSameAsOriginal();
|
||||
|
||||
listener.beforeTestMethod(testContext);
|
||||
assertGlobalObservationRegistryIsSameAsOriginal();
|
||||
|
||||
listener.afterTestMethod(testContext);
|
||||
assertGlobalObservationRegistryIsSameAsOriginal();
|
||||
}
|
||||
|
||||
@Test
|
||||
void observationRegistryIsOverriddenByBeanFromApplicationContext() throws Exception {
|
||||
assertGlobalObservationRegistryIsSameAsOriginal();
|
||||
|
||||
ObservationRegistry testObservationRegistry = ObservationRegistry.create();
|
||||
applicationContext.getDefaultListableBeanFactory().registerSingleton("observationRegistry", testObservationRegistry);
|
||||
|
||||
listener.beforeTestMethod(testContext);
|
||||
ObservationRegistry globalObservationRegistry = globalObservationRegistry();
|
||||
assertThat(globalObservationRegistry)
|
||||
.as("The global ObservationRegistry should have been replaced with the one from the application context")
|
||||
.isNotSameAs(originalObservationRegistry)
|
||||
.isSameAs(testObservationRegistry);
|
||||
|
||||
listener.afterTestMethod(testContext);
|
||||
assertGlobalObservationRegistryIsSameAsOriginal();
|
||||
}
|
||||
|
||||
private void assertGlobalObservationRegistryIsSameAsOriginal() {
|
||||
assertThat(globalObservationRegistry()).isSameAs(originalObservationRegistry);
|
||||
}
|
||||
|
||||
private static ObservationRegistry globalObservationRegistry() {
|
||||
return ObservationThreadLocalAccessor.getInstance().getObservationRegistry();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.springframework.test.context.observation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.test.context.TestContext;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class MicrometerObservationThreadLocalTestExecutionListenerTests {
|
||||
|
||||
ObservationRegistry originalObservationRegistry = ObservationThreadLocalAccessor.getInstance().getObservationRegistry();
|
||||
|
||||
TestContext testContext = mock();
|
||||
|
||||
StaticApplicationContext applicationContext = new StaticApplicationContext();
|
||||
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
MicrometerObservationThreadLocalTestExecutionListener listener = new MicrometerObservationThreadLocalTestExecutionListener();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
willAnswer(invocation -> attributes.put(invocation.getArgument(0), invocation.getArgument(1))).given(testContext).setAttribute(anyString(), any());
|
||||
given(testContext.getAttribute(anyString())).willAnswer(invocation -> attributes.get(invocation.getArgument(0, String.class)));
|
||||
given(testContext.getApplicationContext()).willReturn(applicationContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void observationRegistryShouldNotBeOverridden() throws Exception {
|
||||
listener.beforeTestMethod(testContext);
|
||||
thenObservationRegistryOnOTLAIsSameAsOriginal();
|
||||
listener.afterTestMethod(testContext);
|
||||
thenObservationRegistryOnOTLAIsSameAsOriginal();
|
||||
}
|
||||
|
||||
@Test
|
||||
void observationRegistryOverriddenByBeanFromTestContext() throws Exception {
|
||||
ObservationRegistry newObservationRegistry = ObservationRegistry.create();
|
||||
applicationContext.getDefaultListableBeanFactory().registerSingleton("observationRegistry", newObservationRegistry);
|
||||
|
||||
listener.beforeTestMethod(testContext);
|
||||
ObservationRegistry otlaObservationRegistry = ObservationThreadLocalAccessor.getInstance().getObservationRegistry();
|
||||
then(otlaObservationRegistry)
|
||||
.as("During the test we want the original ObservationRegistry to be replaced with the one present in this application context")
|
||||
.isNotSameAs(originalObservationRegistry)
|
||||
.isSameAs(newObservationRegistry);
|
||||
|
||||
listener.afterTestMethod(testContext);
|
||||
thenObservationRegistryOnOTLAIsSameAsOriginal();
|
||||
}
|
||||
|
||||
private void thenObservationRegistryOnOTLAIsSameAsOriginal() {
|
||||
then(ObservationThreadLocalAccessor.getInstance().getObservationRegistry()).isSameAs(originalObservationRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user