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:
parent
a82659c837
commit
aa2028127f
|
|
@ -12,6 +12,8 @@ by default, exactly in the following order:
|
||||||
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
|
xref:testing/testcontext-framework/application-events.adoc[`ApplicationEvents`].
|
||||||
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
|
* `DependencyInjectionTestExecutionListener`: Provides dependency injection for the test
|
||||||
instance.
|
instance.
|
||||||
|
* `MicrometerObservationRegistryTestExecutionListener`: Provides support for
|
||||||
|
Micrometer's `ObservationRegistry`.
|
||||||
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
* `DirtiesContextTestExecutionListener`: Handles the `@DirtiesContext` annotation for
|
||||||
"`after`" modes.
|
"`after`" modes.
|
||||||
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
* `TransactionalTestExecutionListener`: Provides transactional test execution with
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ package org.springframework.test.context;
|
||||||
* ApplicationEventsTestExecutionListener}</li>
|
* ApplicationEventsTestExecutionListener}</li>
|
||||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
|
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
|
||||||
* DependencyInjectionTestExecutionListener}</li>
|
* DependencyInjectionTestExecutionListener}</li>
|
||||||
|
* <li>{@link org.springframework.test.context.observation.MicrometerObservationRegistryTestExecutionListener
|
||||||
|
* MicrometerObservationRegistryTestExecutionListener}</li>
|
||||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
|
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
|
||||||
* DirtiesContextTestExecutionListener}</li>
|
* DirtiesContextTestExecutionListener}</li>
|
||||||
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
|
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* 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.lang.reflect.Method;
|
||||||
|
|
||||||
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.junit.platform.launcher.TestExecutionListener;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.Conventions;
|
||||||
|
import org.springframework.test.context.TestContext;
|
||||||
|
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code TestExecutionListener} which provides support for Micrometer's
|
||||||
|
* {@link ObservationRegistry}.
|
||||||
|
*
|
||||||
|
* <p>This listener updates the {@link ObservationThreadLocalAccessor} with the
|
||||||
|
* {@code ObservationRegistry} obtained from the test's {@link ApplicationContext},
|
||||||
|
* if present.
|
||||||
|
*
|
||||||
|
* @author Marcin Grzejszczak
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.0.10
|
||||||
|
*/
|
||||||
|
class MicrometerObservationRegistryTestExecutionListener extends AbstractTestExecutionListener {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(MicrometerObservationRegistryTestExecutionListener.class);
|
||||||
|
|
||||||
|
private static final String OBSERVATION_THREAD_LOCAL_ACCESSOR_CLASS_NAME =
|
||||||
|
"io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute name for a {@link TestContext} attribute which contains the
|
||||||
|
* {@link ObservationRegistry} that was previously stored in the
|
||||||
|
* {@link ObservationThreadLocalAccessor}.
|
||||||
|
* <p>After each test method, the previously stored {@code ObservationRegistry}
|
||||||
|
* will be restored. If tests run concurrently this might cause issues unless
|
||||||
|
* the {@code ObservationRegistry} is always the same (which should typically
|
||||||
|
* be the case).
|
||||||
|
*/
|
||||||
|
private static final String PREVIOUS_OBSERVATION_REGISTRY = Conventions.getQualifiedAttributeName(
|
||||||
|
MicrometerObservationRegistryTestExecutionListener.class, "previousObservationRegistry");
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Trigger eager resolution of Micrometer Observation types during static
|
||||||
|
// initialization of this class to ensure that this listener can be properly
|
||||||
|
// skipped when SpringFactoriesLoader attempts to load it, if micrometer-observation
|
||||||
|
// is not in the classpath or if the version of ObservationThreadLocalAccessor
|
||||||
|
// present does not include the getObservationRegistry() method.
|
||||||
|
String errorMessage =
|
||||||
|
"MicrometerObservationRegistryTestExecutionListener requires micrometer-observation 1.10.8 or higher";
|
||||||
|
Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz = Class.forName(OBSERVATION_THREAD_LOCAL_ACCESSOR_CLASS_NAME, true,
|
||||||
|
TestExecutionListener.class.getClassLoader());
|
||||||
|
}
|
||||||
|
catch (Throwable ex) {
|
||||||
|
throw new IllegalStateException(errorMessage, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Method method = ReflectionUtils.findMethod(clazz, "getObservationRegistry");
|
||||||
|
Assert.state(method != null, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code 2500}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final int getOrder() {
|
||||||
|
return 2500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the test's {@link ApplicationContext} contains an {@link ObservationRegistry}
|
||||||
|
* bean, this method retrieves the {@code ObservationRegistry} currently stored
|
||||||
|
* in {@link ObservationThreadLocalAccessor}, saves a reference to the original
|
||||||
|
* registry as a {@link TestContext} attribute (to be restored in
|
||||||
|
* {@link #afterTestMethod(TestContext)}), and sets the registry from the test's
|
||||||
|
* {@code ApplicationContext} in {@link ObservationThreadLocalAccessor}.
|
||||||
|
* @param testContext the test context for the test; never {@code null}
|
||||||
|
* @see #afterTestMethod(TestContext)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void beforeTestMethod(TestContext testContext) {
|
||||||
|
testContext.getApplicationContext().getBeanProvider(ObservationRegistry.class)
|
||||||
|
.ifAvailable(registry -> {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("""
|
||||||
|
Registering ObservationRegistry from ApplicationContext in \
|
||||||
|
ObservationThreadLocalAccessor for test class \
|
||||||
|
""" + testContext.getTestClass().getName());
|
||||||
|
}
|
||||||
|
ObservationThreadLocalAccessor accessor = ObservationThreadLocalAccessor.getInstance();
|
||||||
|
testContext.setAttribute(PREVIOUS_OBSERVATION_REGISTRY, accessor.getObservationRegistry());
|
||||||
|
accessor.setObservationRegistry(registry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the original {@link ObservationRegistry} that was saved in
|
||||||
|
* {@link #beforeTestMethod(TestContext)} and sets it in
|
||||||
|
* {@link ObservationThreadLocalAccessor}.
|
||||||
|
* @param testContext the test context for the test; never {@code null}
|
||||||
|
* @see #beforeTestMethod(TestContext)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterTestMethod(TestContext testContext) {
|
||||||
|
ObservationRegistry previousObservationRegistry =
|
||||||
|
(ObservationRegistry) testContext.removeAttribute(PREVIOUS_OBSERVATION_REGISTRY);
|
||||||
|
if (previousObservationRegistry != null) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Restoring ObservationRegistry in ObservationThreadLocalAccessor for test class " +
|
||||||
|
testContext.getTestClass().getName());
|
||||||
|
}
|
||||||
|
ObservationThreadLocalAccessor.getInstance().setObservationRegistry(previousObservationRegistry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,92 +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 io.micrometer.observation.ObservationRegistry;
|
|
||||||
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.core.Conventions;
|
|
||||||
import org.springframework.test.context.TestContext;
|
|
||||||
import org.springframework.test.context.TestExecutionListener;
|
|
||||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@code ObservationThreadLocalTestExecutionListener} is an implementation of the {@link TestExecutionListener}
|
|
||||||
* SPI that updates the {@link ObservationThreadLocalAccessor} with the {@link ObservationRegistry}
|
|
||||||
* taken from the {@link ApplicationContext} present in the {@link TestContext}.
|
|
||||||
*
|
|
||||||
* <p>This implementation is not thread-safe.
|
|
||||||
*
|
|
||||||
* @author Marcin Grzejszczak
|
|
||||||
* @since 6.1
|
|
||||||
*/
|
|
||||||
public class MicrometerObservationThreadLocalTestExecutionListener extends AbstractTestExecutionListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attribute name for a {@link TestContext} attribute which contains the previously
|
|
||||||
* set {@link ObservationRegistry} on the {@link ObservationThreadLocalAccessor}.
|
|
||||||
* <p>After all tests from the current test class have completed, the previously stored {@link ObservationRegistry}
|
|
||||||
* will be restored. If tests are ran concurrently this might cause issues
|
|
||||||
* unless the {@link ObservationRegistry} is always the same (which should be the case most frequently).
|
|
||||||
*/
|
|
||||||
private static final String PREVIOUS_OBSERVATION_REGISTRY = Conventions.getQualifiedAttributeName(
|
|
||||||
MicrometerObservationThreadLocalTestExecutionListener.class, "previousObservationRegistry");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the current {@link ObservationRegistry} stored
|
|
||||||
* on {@link ObservationThreadLocalAccessor} instance and stores it
|
|
||||||
* in the {@link TestContext} attributes and overrides it with
|
|
||||||
* one stored in {@link ApplicationContext} associated with
|
|
||||||
* the {@link TestContext}.
|
|
||||||
* @param testContext the test context for the test; never {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void beforeTestMethod(TestContext testContext) {
|
|
||||||
testContext.setAttribute(PREVIOUS_OBSERVATION_REGISTRY,
|
|
||||||
ObservationThreadLocalAccessor.getInstance().getObservationRegistry());
|
|
||||||
testContext.getApplicationContext()
|
|
||||||
.getBeanProvider(ObservationRegistry.class)
|
|
||||||
.ifAvailable(observationRegistry ->
|
|
||||||
ObservationThreadLocalAccessor.getInstance()
|
|
||||||
.setObservationRegistry(observationRegistry));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the previously stored {@link ObservationRegistry} and sets it back
|
|
||||||
* on the {@link ObservationThreadLocalAccessor} instance.
|
|
||||||
* @param testContext the test context for the test; never {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void afterTestMethod(TestContext testContext) {
|
|
||||||
ObservationRegistry previousObservationRegistry =
|
|
||||||
(ObservationRegistry) testContext.getAttribute(PREVIOUS_OBSERVATION_REGISTRY);
|
|
||||||
if (previousObservationRegistry != null) {
|
|
||||||
ObservationThreadLocalAccessor.getInstance()
|
|
||||||
.setObservationRegistry(previousObservationRegistry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code 3500}.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final int getOrder() {
|
|
||||||
return 3500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,11 +5,11 @@ org.springframework.test.context.TestExecutionListener = \
|
||||||
org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\
|
org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\
|
||||||
org.springframework.test.context.event.ApplicationEventsTestExecutionListener,\
|
org.springframework.test.context.event.ApplicationEventsTestExecutionListener,\
|
||||||
org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
|
org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
|
||||||
|
org.springframework.test.context.observation.MicrometerObservationRegistryTestExecutionListener,\
|
||||||
org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
|
org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
|
||||||
org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
|
org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
|
||||||
org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener,\
|
org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener,\
|
||||||
org.springframework.test.context.event.EventPublishingTestExecutionListener,\
|
org.springframework.test.context.event.EventPublishingTestExecutionListener
|
||||||
org.springframework.test.context.observation.MicrometerObservationThreadLocalTestExecutionListener
|
|
||||||
|
|
||||||
# Default ContextCustomizerFactory implementations for the Spring TestContext Framework
|
# Default ContextCustomizerFactory implementations for the Spring TestContext Framework
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.ApplicationEventsTestExecutionListener;
|
||||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
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.AbstractTestExecutionListener;
|
||||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||||
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
|
||||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||||
import org.springframework.test.context.web.ServletTestExecutionListener;
|
import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
@ -57,14 +57,17 @@ import static org.springframework.test.context.TestExecutionListeners.MergeMode.
|
||||||
*/
|
*/
|
||||||
class TestExecutionListenersTests {
|
class TestExecutionListenersTests {
|
||||||
|
|
||||||
|
private static final Class<?> micrometerListenerClass =
|
||||||
|
ClassUtils.resolveClassName("org.springframework.test.context.observation.MicrometerObservationRegistryTestExecutionListener", null);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void defaultListeners() {
|
void defaultListeners() {
|
||||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,//
|
List<Class<?>> expected = asList(ServletTestExecutionListener.class,//
|
||||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||||
ApplicationEventsTestExecutionListener.class,//
|
ApplicationEventsTestExecutionListener.class,//
|
||||||
DependencyInjectionTestExecutionListener.class,//
|
DependencyInjectionTestExecutionListener.class,//
|
||||||
|
micrometerListenerClass,//
|
||||||
DirtiesContextTestExecutionListener.class,//
|
DirtiesContextTestExecutionListener.class,//
|
||||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
|
||||||
TransactionalTestExecutionListener.class,//
|
TransactionalTestExecutionListener.class,//
|
||||||
SqlScriptsTestExecutionListener.class,//
|
SqlScriptsTestExecutionListener.class,//
|
||||||
EventPublishingTestExecutionListener.class
|
EventPublishingTestExecutionListener.class
|
||||||
|
|
@ -82,8 +85,8 @@ class TestExecutionListenersTests {
|
||||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||||
ApplicationEventsTestExecutionListener.class,//
|
ApplicationEventsTestExecutionListener.class,//
|
||||||
DependencyInjectionTestExecutionListener.class,//
|
DependencyInjectionTestExecutionListener.class,//
|
||||||
|
micrometerListenerClass,//
|
||||||
DirtiesContextTestExecutionListener.class,//
|
DirtiesContextTestExecutionListener.class,//
|
||||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
|
||||||
TransactionalTestExecutionListener.class,//
|
TransactionalTestExecutionListener.class,//
|
||||||
SqlScriptsTestExecutionListener.class,//
|
SqlScriptsTestExecutionListener.class,//
|
||||||
EventPublishingTestExecutionListener.class
|
EventPublishingTestExecutionListener.class
|
||||||
|
|
@ -100,8 +103,8 @@ class TestExecutionListenersTests {
|
||||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||||
ApplicationEventsTestExecutionListener.class,//
|
ApplicationEventsTestExecutionListener.class,//
|
||||||
DependencyInjectionTestExecutionListener.class,//
|
DependencyInjectionTestExecutionListener.class,//
|
||||||
|
micrometerListenerClass,//
|
||||||
DirtiesContextTestExecutionListener.class,//
|
DirtiesContextTestExecutionListener.class,//
|
||||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
|
||||||
TransactionalTestExecutionListener.class,
|
TransactionalTestExecutionListener.class,
|
||||||
SqlScriptsTestExecutionListener.class,//
|
SqlScriptsTestExecutionListener.class,//
|
||||||
EventPublishingTestExecutionListener.class,//
|
EventPublishingTestExecutionListener.class,//
|
||||||
|
|
@ -120,8 +123,8 @@ class TestExecutionListenersTests {
|
||||||
ApplicationEventsTestExecutionListener.class,//
|
ApplicationEventsTestExecutionListener.class,//
|
||||||
DependencyInjectionTestExecutionListener.class,//
|
DependencyInjectionTestExecutionListener.class,//
|
||||||
BarTestExecutionListener.class,//
|
BarTestExecutionListener.class,//
|
||||||
|
micrometerListenerClass,//
|
||||||
DirtiesContextTestExecutionListener.class,//
|
DirtiesContextTestExecutionListener.class,//
|
||||||
MicrometerObservationThreadLocalTestExecutionListener.class,//
|
|
||||||
TransactionalTestExecutionListener.class,//
|
TransactionalTestExecutionListener.class,//
|
||||||
SqlScriptsTestExecutionListener.class,//
|
SqlScriptsTestExecutionListener.class,//
|
||||||
EventPublishingTestExecutionListener.class
|
EventPublishingTestExecutionListener.class
|
||||||
|
|
@ -366,9 +369,9 @@ class TestExecutionListenersTests {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOrder() {
|
public int getOrder() {
|
||||||
// 2500 is between DependencyInjectionTestExecutionListener (2000) and
|
// 2250 is between DependencyInjectionTestExecutionListener (2000) and
|
||||||
// DirtiesContextTestExecutionListener (3000)
|
// MicrometerObservationRegistryTestExecutionListener (2500)
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
<Logger name="org.springframework.test.context.cache" level="warn" />
|
<Logger name="org.springframework.test.context.cache" level="warn" />
|
||||||
<Logger name="org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate" level="info" />
|
<Logger name="org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate" level="info" />
|
||||||
<Logger name="org.springframework.test.context.junit4.rules" level="warn" />
|
<Logger name="org.springframework.test.context.junit4.rules" level="warn" />
|
||||||
|
<Logger name="org.springframework.test.context.observation" level="warn" />
|
||||||
<Logger name="org.springframework.test.context.transaction.TransactionalTestExecutionListener" level="warn" />
|
<Logger name="org.springframework.test.context.transaction.TransactionalTestExecutionListener" level="warn" />
|
||||||
<Logger name="org.springframework.test.context.web" level="warn" />
|
<Logger name="org.springframework.test.context.web" level="warn" />
|
||||||
<!-- The following must be kept at DEBUG in order to test SPR-14363. -->
|
<!-- The following must be kept at DEBUG in order to test SPR-14363. -->
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue