From e25cdb363ca8f1085d5714f331c8b83148d2b4d3 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 16 Sep 2020 19:24:45 -0700 Subject: [PATCH] Provide mock stubbing support for GemfireRepository.count() method when GemFire Mock Objects is enabled. --- .../GemFireMockObjectsConfiguration.java | 82 +++++++++- ...gMockRegionOperationsIntegrationTests.java | 148 ++++++++++++++++++ 2 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/GemfireTemplateAndGemfireRepositoryUsingMockRegionOperationsIntegrationTests.java diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java index 539814e..2a11f37 100644 --- a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/mock/annotation/GemFireMockObjectsConfiguration.java @@ -16,8 +16,14 @@ package org.springframework.data.gemfire.tests.mock.annotation; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.util.Optional; +import org.apache.geode.cache.Region; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @@ -26,10 +32,17 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; +import org.springframework.data.gemfire.repository.GemfireRepository; +import org.springframework.data.gemfire.repository.support.SimpleGemfireRepository; import org.springframework.data.gemfire.tests.mock.beans.factory.config.GemFireMockObjectsBeanPostProcessor; import org.springframework.data.gemfire.tests.mock.context.event.DestroyGemFireMockObjectsApplicationListener; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.test.context.event.AfterTestClassEvent; +import org.springframework.util.Assert; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; /** * The {@link GemFireMockObjectsConfiguration} class is a Spring {@link Configuration @Configuration} class @@ -90,13 +103,78 @@ public class GemFireMockObjectsConfiguration extends AbstractAnnotationConfigSup return this.useSingletonCache; } + @Bean + public ApplicationListener destroyGemFireMockObjectsApplicationListener() { + return DestroyGemFireMockObjectsApplicationListener.newInstance(getConfiguredDestroyEventTypes()); + } + @Bean public BeanPostProcessor gemfireMockObjectsBeanPostProcessor() { return GemFireMockObjectsBeanPostProcessor.newInstance(isUseSingletonCacheConfigured()); } @Bean - public ApplicationListener destroyGemFireMockObjectsApplicationListener() { - return DestroyGemFireMockObjectsApplicationListener.newInstance(getConfiguredDestroyEventTypes()); + public BeanPostProcessor gemfireRepositoryBeanPostProcessor() { + + return new BeanPostProcessor() { + + public Object postProcessAfterInitialization(@NonNull Object bean, String beanName) throws BeansException { + + if (bean instanceof GemfireRepository) { + + getRegion(bean) + .ifPresent(region -> { + if (bean instanceof Advised) { + + Advised advisedBean = (Advised) bean; + + advisedBean.addAdvice(0, new CountMethodInterceptor(region)); + } + }); + } + + return bean; + } + + private Optional> getRegion(@Nullable Object bean) { + + return Optional.ofNullable(bean) + .map(AopProxyUtils::getSingletonTarget) + .filter(SimpleGemfireRepository.class::isInstance) + .map(SimpleGemfireRepository.class::cast) + .map(SimpleGemfireRepository::getRegion); + } + }; + } + + @SuppressWarnings("rawtypes") + private static class CountMethodInterceptor implements MethodInterceptor { + + private static final String COUNT_METHOD_NAME = "count"; + + private final Region region; + + private CountMethodInterceptor(@NonNull Region region) { + + Assert.notNull(region, "Region must not be null"); + + this.region = region; + } + + private @NonNull Region getRegion() { + return this.region; + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + + return isCountMethod(method) ? Long.valueOf(getRegion().size()) : invocation.proceed(); + } + + private boolean isCountMethod(Method method) { + return method != null && COUNT_METHOD_NAME.equals(method.getName()); + } } } diff --git a/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/GemfireTemplateAndGemfireRepositoryUsingMockRegionOperationsIntegrationTests.java b/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/GemfireTemplateAndGemfireRepositoryUsingMockRegionOperationsIntegrationTests.java new file mode 100644 index 0000000..0a00ef2 --- /dev/null +++ b/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/tests/mock/GemfireTemplateAndGemfireRepositoryUsingMockRegionOperationsIntegrationTests.java @@ -0,0 +1,148 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + +import javax.annotation.Resource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.apache.geode.cache.Region; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.data.annotation.Id; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions; +import org.springframework.data.gemfire.mapping.GemfireMappingContext; +import org.springframework.data.gemfire.repository.GemfireRepository; +import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean; +import org.springframework.data.gemfire.tests.mock.annotation.EnableGemFireMockObjects; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Integration Tests for SDG's {@link GemfireTemplate} and SD[G] {@link GemfireRepository Repositories}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.GemfireTemplate + * @see org.springframework.data.gemfire.repository.GemfireRepository + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 0.0.19 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +public class GemfireTemplateAndGemfireRepositoryUsingMockRegionOperationsIntegrationTests { + + private final Customer jonDoe = Customer.newCustomer(1L, "Jon Doe"); + + @Autowired + private CustomerRepository customerRepository; + + @Autowired + @Qualifier("customersTemplate") + private GemfireTemplate customersTemplate; + + @Resource(name = "Customers") + private Region customers; + + @Before + public void setup() { + + assertThat(this.customers).isNotNull(); + assertThat(this.customers.getName()).isEqualTo("Customers"); + + this.customers.put(this.jonDoe.getId(), this.jonDoe); + + assertThat(this.customers).hasSize(1); + assertThat(this.customers.get(this.jonDoe.getId())).isEqualTo(this.jonDoe); + } + + @Test + public void gemfireRepositoryCountIsCorrect() { + assertThat(this.customerRepository.count()).isEqualTo(1L); + } + + @Test + public void gemfireRepositoryFindByIdIsCorrect() { + + assertThat(this.customerRepository.findById(this.jonDoe.getId()).orElse(null)).isEqualTo(this.jonDoe); + + verify(this.customers, atLeastOnce()).get(eq(this.jonDoe.getId())); + } + + @Test + public void gemfireTemplateGetIsCorrect() { + + assertThat(this.customersTemplate.get(this.jonDoe.getId())).isEqualTo(this.jonDoe); + + verify(this.customers, atLeastOnce()).get(eq(this.jonDoe.getId())); + } + + @EnableGemFireMockObjects + @ClientCacheApplication + @EnableEntityDefinedRegions(basePackageClasses = Customer.class) + static class TestConfiguration { + + @Bean + GemfireTemplate customersTemplate(@Qualifier("Customers") Region customersRegion) { + return new GemfireTemplate(customersRegion); + } + + @Bean + GemfireRepositoryFactoryBean customersRepositoryFactoryBean() { + + GemfireRepositoryFactoryBean repositoryFactoryBean = + new GemfireRepositoryFactoryBean<>(CustomerRepository.class); + + repositoryFactoryBean.setGemfireMappingContext(new GemfireMappingContext()); + + return repositoryFactoryBean; + } + } + + @Data + @RequiredArgsConstructor(staticName = "newCustomer") + @org.springframework.data.gemfire.mapping.annotation.Region("Customers") + static class Customer { + + @Id @NonNull + private Long id; + + @NonNull + private String name; + + } + + interface CustomerRepository extends GemfireRepository { } + +}