From 39f36ce9d07ffa3aacbd8ba3fbf6f12d5da948ac Mon Sep 17 00:00:00 2001 From: John Blum Date: Thu, 21 May 2020 21:45:13 -0700 Subject: [PATCH] Add Spring ApplicationListener implementation to destroy all GemFire/Geode Mock Objects when configured ApplicationEvents are fired in a Spring context. Resolves gh-20. --- ...GemFireMockObjectsApplicationListener.java | 164 ++++++++++++++++++ ...ckObjectsApplicationListenerUnitTests.java | 129 ++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListener.java create mode 100644 spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListenerUnitTests.java diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListener.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListener.java new file mode 100644 index 0000000..9330234 --- /dev/null +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListener.java @@ -0,0 +1,164 @@ +/* + * Copyright 2019 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.data.gemfire.tests.mock.context.event; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * A Spring {@link ApplicationListener} interface implementation handling Spring {@link ApplicationContext} (container) + * {@link ApplicationEvent ApplicationEvents} by destroying all GemFire/Geode {@link Object Mock Objects}. + * + * @author John Blum + * @see org.springframework.context.ApplicationContext + * @see org.springframework.context.ApplicationEvent + * @see org.springframework.context.ApplicationListener + * @see org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport + * @since 0.0.16 + */ +@SuppressWarnings("unused") +public class DestroyGemFireMockObjectsApplicationListener implements ApplicationListener { + + /** + * Null-safe factory method used to construct a new instance of {@link DestroyGemFireMockObjectsApplicationListener} + * initialized with an array of {@link ApplicationEvent} {@link Class types} that will trigger the destruction + * of all GemFire/Geode {@link Object Mock Objects}. + * + * @param destroyEventTypes array of {@link ApplicationEvent} {@link Class types} that will trigger the destruction + * of all GemFire/Geode {@link Object Mock Objects}. + * @return an new instance of {@link DestroyGemFireMockObjectsApplicationListener}. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Class + * @see #newInstance(Iterable) + */ + @SuppressWarnings("unchecked") + public static DestroyGemFireMockObjectsApplicationListener newInstance( + @Nullable Class... destroyEventTypes) { + + return newInstance(Arrays.asList(ArrayUtils.nullSafeArray(destroyEventTypes, Class.class))); + } + + /** + * Null-safe factory method used to construct a new instance of {@link DestroyGemFireMockObjectsApplicationListener} + * initialized with an {@link Iterable} of {@link ApplicationEvent} {@link Class types} that will trigger + * the destruction of all GemFire/Geode {@link Object Mock Objects}. + * + * @param destroyEventTypes {@link Iterable} of {@link ApplicationEvent} {@link Class types} that will trigger + * the destruction of all GemFire/Geode {@link Object Mock Objects}. + * @return an new instance of {@link DestroyGemFireMockObjectsApplicationListener}. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Class + * @see java.lang.Iterable + * @see #newInstance(Iterable) + */ + public static DestroyGemFireMockObjectsApplicationListener newInstance( + @Nullable Iterable> destroyEventTypes) { + + return new DestroyGemFireMockObjectsApplicationListener(destroyEventTypes); + } + + + private final Set> configuredDestroyEventTypes; + + /** + * Constructs a new instance of {@link DestroyGemFireMockObjectsApplicationListener} initialized with the given + * {@link ApplicationEvent} {@link Class types} that when fired will trigger the destruction of all GemFire/Geode + * {@link Object Mock Objects}. + * + * @param destroyEventTypes {@link Iterable} of {@link ApplicationEvent} {@link Class types} that will trigger + * the destruction of all GemFire/Geode {@link Object Mock Objects}. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Iterable + */ + public DestroyGemFireMockObjectsApplicationListener( + @Nullable Iterable> destroyEventTypes) { + + Set> resolvedDestroyEventTypes = + StreamSupport.stream(CollectionUtils.nullSafeIterable(destroyEventTypes).spliterator(), false) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + this.configuredDestroyEventTypes = Collections.unmodifiableSet(resolvedDestroyEventTypes); + } + + /** + * Returns the configured {@link Set} of {@link ApplicationEvent} {@link Class types} that trigger the destruction + * of all GemFire/Geode {@link Object Mock Objects}. + * + * @return the configured {@link Set} of {@link ApplicationEvent} {@link Class types} that trigger the destruction + * of all GemFire/Geode {@link Object Mock Objects}. + * @see org.springframework.context.ApplicationEvent + * @see java.lang.Class + * @see java.util.Set + */ + protected @NonNull Set> getConfiguredDestroyEventTypes() { + return this.configuredDestroyEventTypes; + } + + /** + * @inheritDoc + */ + @Override + public void onApplicationEvent(@NonNull ApplicationEvent event) { + + if (isDestroyEvent(event)) { + destroyGemFireMockObjects(); + } + } + + /** + * Null-safe method to determine whether the given {@link ApplicationEvent} will cause the destruction + * of all GemFire/Geode {@link Object Mock Objects}. + * + * @param event {@link ApplicationEvent} to evaluate. + * @return a boolean value indicating whether the given {@link ApplicationEvent} will cause the destruction + * of all GemFire/Geode {@link Object Mock Objects}. + * @see org.springframework.context.ApplicationEvent + * @see #getConfiguredDestroyEventTypes() + */ + protected boolean isDestroyEvent(@Nullable ApplicationEvent event) { + + for (Class configuredDestroyEventType : getConfiguredDestroyEventTypes()) { + if (configuredDestroyEventType.isInstance(event)) { + return true; + } + } + + return false; + } + + /** + * Destroys all GemFire/Geode {@link Object Mock Objects}. + * + * @see org.springframework.data.gemfire.tests.mock.GemFireMockObjectsSupport#destroy() + */ + protected void destroyGemFireMockObjects() { + GemFireMockObjectsSupport.destroy(); + } +} diff --git a/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListenerUnitTests.java b/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListenerUnitTests.java new file mode 100644 index 0000000..3830ca3 --- /dev/null +++ b/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/context/event/DestroyGemFireMockObjectsApplicationListenerUnitTests.java @@ -0,0 +1,129 @@ +/* + * Copyright 2019 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.data.gemfire.tests.mock.context.event; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.event.AfterTestExecutionEvent; +import org.springframework.test.context.event.BeforeTestExecutionEvent; + +/** + * The DestroyGemFireMockObjectsApplicationListenerUnitTests class... + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.context.ApplicationEvent + * @see org.springframework.data.gemfire.tests.mock.context.event.DestroyGemFireMockObjectsApplicationListener + * @see org.springframework.test.context.TestContext + * @since 0.0.16 + */ +public class DestroyGemFireMockObjectsApplicationListenerUnitTests { + + private DestroyGemFireMockObjectsApplicationListener listener; + + @Before + public void configureListener() { + + Iterable> destroyEvents = + Arrays.asList(ContextClosedEvent.class, AfterTestExecutionEvent.class); + + this.listener = spy(new DestroyGemFireMockObjectsApplicationListener(destroyEvents)); + + doNothing().when(this.listener).destroyGemFireMockObjects(); + } + + @Test + public void destroysGemFireMockObjectsOnAfterTestExecutionEvent() { + + AfterTestExecutionEvent mockEvent = mock(AfterTestExecutionEvent.class); + + this.listener.onApplicationEvent(mockEvent); + + verify(this.listener, times(1)).destroyGemFireMockObjects(); + } + + @Test + public void destroysGemFireMockObjectsOnAfterTestExecutionEventExtension() { + + AfterTestExecutionEventExtension mockEvent = mock(AfterTestExecutionEventExtension.class); + + this.listener.onApplicationEvent(mockEvent); + + verify(this.listener, times(1)).destroyGemFireMockObjects(); + } + + @Test + public void destroysGemFireMockObjectsOnContextClosedEvent() { + + ContextClosedEvent mockEvent = mock(ContextClosedEvent.class); + + this.listener.onApplicationEvent(mockEvent); + + verify(this.listener, times(1)).destroyGemFireMockObjects(); + } + + @Test + public void doesNotDestroyGemFireMockObjectsOnBeforeTestExecutionEvent() { + + BeforeTestExecutionEvent mockEvent = mock(BeforeTestExecutionEvent.class); + + this.listener.onApplicationEvent(mockEvent); + + verify(this.listener, never()).destroyGemFireMockObjects(); + } + + @Test + public void doesNotDestroyGemFireMockObjectsOnContextRefreshedEvent() { + + ContextRefreshedEvent mockEvent = mock(ContextRefreshedEvent.class); + + this.listener.onApplicationEvent(mockEvent); + + verify(this.listener, never()).destroyGemFireMockObjects(); + } + + @Test + public void doesNotDestroyGemFireMockObjectsOnRandomApplicationEvent() { + + ApplicationEvent mockEvent = mock(ApplicationEvent.class); + + this.listener.onApplicationEvent(mockEvent); + + verify(this.listener, never()).destroyGemFireMockObjects(); + } + + static class AfterTestExecutionEventExtension extends AfterTestExecutionEvent { + + AfterTestExecutionEventExtension(TestContext context) { + super(context); + } + } +}