Introduce TestExecutionListener for Micrometer ObservationRegistry

Prior to this commit, there was no way to specify the
ObservationRegistry that is registered in the given test's
ApplicationContext as the one that should be used by Micrometer's
ObservationThreadLocalAccessor for context propagation.

This commit introduces a TestExecutionListener for Micrometer's
ObservationRegistry in the Spring TestContext Framework. Specifically,
this listener obtains the ObservationRegistry registered in the test's
ApplicationContext, stores it in ObservationThreadLocalAccessor for the
duration of each test method execution, and restores the original
ObservationRegistry in ObservationThreadLocalAccessor after each test.

Co-authored-by: Sam Brannen <sam@sambrannen.com>
See gh-30658
This commit is contained in:
Marcin Grzejszczak
2023-06-12 20:25:38 +02:00
committed by Sam Brannen
parent 3415b04c73
commit a82659c837
6 changed files with 194 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ 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;
@@ -63,6 +64,7 @@ class TestExecutionListenersTests {
ApplicationEventsTestExecutionListener.class,//
DependencyInjectionTestExecutionListener.class,//
DirtiesContextTestExecutionListener.class,//
MicrometerObservationThreadLocalTestExecutionListener.class,//
TransactionalTestExecutionListener.class,//
SqlScriptsTestExecutionListener.class,//
EventPublishingTestExecutionListener.class
@@ -81,6 +83,7 @@ class TestExecutionListenersTests {
ApplicationEventsTestExecutionListener.class,//
DependencyInjectionTestExecutionListener.class,//
DirtiesContextTestExecutionListener.class,//
MicrometerObservationThreadLocalTestExecutionListener.class,//
TransactionalTestExecutionListener.class,//
SqlScriptsTestExecutionListener.class,//
EventPublishingTestExecutionListener.class
@@ -98,6 +101,7 @@ class TestExecutionListenersTests {
ApplicationEventsTestExecutionListener.class,//
DependencyInjectionTestExecutionListener.class,//
DirtiesContextTestExecutionListener.class,//
MicrometerObservationThreadLocalTestExecutionListener.class,//
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class,//
EventPublishingTestExecutionListener.class,//
@@ -117,6 +121,7 @@ class TestExecutionListenersTests {
DependencyInjectionTestExecutionListener.class,//
BarTestExecutionListener.class,//
DirtiesContextTestExecutionListener.class,//
MicrometerObservationThreadLocalTestExecutionListener.class,//
TransactionalTestExecutionListener.class,//
SqlScriptsTestExecutionListener.class,//
EventPublishingTestExecutionListener.class

View File

@@ -0,0 +1,84 @@
/*
* 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);
}
}