All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.wcm.testing.mock.aem.junit5.AemContextExtension Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2018 wcm.io
 * %%
 * 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
 *
 *      http://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.
 * #L%
 */
package io.wcm.testing.mock.aem.junit5;

import static io.wcm.testing.mock.aem.junit5.ReflectionUtil.getAnnotatedMethod;
import static io.wcm.testing.mock.aem.junit5.ReflectionUtil.getField;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;

/**
 * JUnit 5 extension that allows to inject {@link AemContext} (or subclasses of it) parameters in test methods,
 * and ensures that the context is set up and teared down properly for each test method.
 */
public final class AemContextExtension implements ParameterResolver, TestInstancePostProcessor,
    BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, AfterTestExecutionCallback {

  /**
   * Checks if test class has a {@link AemContext} or derived field.
   * If it has and is not instantiated, create an new {@link AemContext} and store it in the field.
   * If it is already instantiated reuse this instance and use it for all test methods.
   */
  @Override
  public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) throws Exception {
    if (!isBeforeAllContext(extensionContext)) {
      Field aemContextField = getField(testInstance, AemContext.class);
      if (aemContextField != null) {
        setAemContextInStore(extensionContext, aemContextField, testInstance);
      }
    }
  }

  private void setAemContextInStore(@NotNull ExtensionContext extensionContext,
      @NotNull Field aemContextField, @Nullable Object testInstance) throws IllegalAccessException {
    AemContext aemContext = (AemContext)aemContextField.get(testInstance);
    if (aemContext != null) {
      if (!aemContext.isSetUp()) {
        aemContext.setUpContext();
      }
      AemContextStore.storeAemContext(extensionContext, aemContext);
    }
    else {
      aemContext = AemContextStore.getOrCreateAemContext(extensionContext, Optional.of(aemContextField.getType()));
      aemContextField.set(testInstance, aemContext);
    }
  }

  /**
   * Support parameter injection for test methods of parameter type is derived from {@link AemContext}.
   */
  @Override
  public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
    return AemContext.class.isAssignableFrom(parameterContext.getParameter().getType());
  }

  /**
   * Resolve (or create) {@link AemContext} instance for test method parameter.
   */
  @Override
  public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
    AemContext aemContext = AemContextStore.getOrCreateAemContext(extensionContext,
        getAemContextType(parameterContext, extensionContext));
    if (paramIsNotInstanceOfExistingContext(parameterContext, aemContext)) {
      throw new ParameterResolutionException(
          "Found AemContext instance of type: " + aemContext.getClass().getName() + "\n"
              + "Required is: " + parameterContext.getParameter().getType().getName() + "\n"
              + "Verify that all test lifecycle methods (@BeforeEach, @Test, @AfterEach) "
              + "use the same AemContext type.");
    }
    return aemContext;
  }

  @Override
  public void beforeAll(ExtensionContext extensionContext) throws Exception {
    if (isBeforeAllContext(extensionContext)) {
      Field aemContextField = getField(extensionContext.getRequiredTestClass(), AemContext.class);
      if (aemContextField != null) {
        setAemContextInStore(extensionContext, aemContextField, null);
      }
      applyAemContext(extensionContext, aemContext ->
        // call context plugins setup after @BeforeAll methods were called
        /* please note: in JUnit5 there is no callback to be called after all @BeforeAll methods are called
         * so we call it before @BeforeAll execution to make sure the plugin code is called at all */
        aemContext.getContextPlugins().executeAfterSetUpCallback(aemContext)
      );
    }
  }

  @Override
  public void beforeEach(ExtensionContext extensionContext) {
    if (!isBeforeAllContext(extensionContext)) {
      applyAemContext(extensionContext, aemContext ->
        // call context plugins setup after @BeforeEach methods were called
        aemContext.getContextPlugins().executeAfterSetUpCallback(aemContext)
      );
    }
  }

  @Override
  public void afterTestExecution(ExtensionContext extensionContext) {
    if (!isBeforeAllContext(extensionContext)) {
      applyAemContext(extensionContext, aemContext ->
        // call context plugins setup before @AfterEach methods are called
        aemContext.getContextPlugins().executeBeforeTearDownCallback(aemContext)
      );
    }
  }

  @Override
  public void afterEach(ExtensionContext extensionContext) {
    if (!isBeforeAllContext(extensionContext)) {
      applyAemContext(extensionContext, aemContext -> {
        // call context plugins setup after @AfterEach methods were called
        aemContext.getContextPlugins().executeAfterTearDownCallback(aemContext);

        // tear down and remove context
        aemContext.tearDownContext();
        AemContextStore.removeAemContext(extensionContext);
      });
    }
  }

  @Override
  public void afterAll(ExtensionContext extensionContext) throws Exception {
    if (isBeforeAllContext(extensionContext)) {
      applyAemContext(extensionContext, aemContext -> {
        // call context plugins setup before @AfterAll methods are called
        /* please note: in JUnit5 there is no callback to be called before all @AfterAll methods are called
         * so we call it after @AfterAll execution to make sure the plugin code is called at all */
        aemContext.getContextPlugins().executeBeforeTearDownCallback(aemContext);

        // call context plugins setup after @AfterAll methods were called
        aemContext.getContextPlugins().executeAfterTearDownCallback(aemContext);

        // tear down and remove context
        aemContext.tearDownContext();
        AemContextStore.removeAemContext(extensionContext);
      });
    }
  }

  private void applyAemContext(ExtensionContext extensionContext, Consumer consumer) {
    AemContext aemContext = AemContextStore.getAemContext(extensionContext);
    if (aemContext != null) {
      consumer.accept(aemContext);
    }
  }

  private Optional> getAemContextType(ParameterContext parameterContext, ExtensionContext extensionContext) {
    // If a @BeforeEach or @AfterEach method has only a generic AemContext parameter check if
    // test method has a more specific parameter and use this
    if (isTestInstance(extensionContext) && isAbstractAemContext(parameterContext)) {
      return getParameterFromTestMethod(extensionContext, AemContext.class);
    }
    else {
      return Optional.of(parameterContext.getParameter().getType());
    }
  }

  /**
   * On @BeforeAll is no test instance available
   * @return {@code true} if test instance is available
   */
  private boolean isTestInstance(ExtensionContext extensionContext) {
    return extensionContext.getTestInstance().isPresent();
  }

  private boolean isAbstractAemContext(ParameterContext parameterContext) {
    return parameterContext.getParameter().getType().equals(AemContext.class);
  }

  private boolean paramIsNotInstanceOfExistingContext(ParameterContext parameterContext, AemContext aemContext) {
    return !parameterContext.getParameter().getType().isInstance(aemContext);
  }

  private Optional> getParameterFromTestMethod(ExtensionContext extensionContext, Class type) {
    return Arrays.stream(extensionContext.getRequiredTestMethod().getParameterTypes())
        .filter(type::isAssignableFrom)
        .findFirst();
  }

  /**
   * 

* Checks if a "before-all" context is used in this class. *

*

* In this case the context is initialized/set up once before all tests, and teared down once after all tests. * Otherwise setup and teardown of the context happens for each test run. *

*

* The "before-all" state is assumed if a) a static AemContext field exists or b) a method annotated with * '@BeforeAll' exists with AemContext parameter. *

* @param extensionContext Extension context * @return true for "before-all" context. */ private boolean isBeforeAllContext(@NotNull ExtensionContext extensionContext) { Boolean state = AemContextStore.getBeforeAllState(extensionContext); if (state == null) { state = false; Class testClass = extensionContext.getRequiredTestClass(); // check for static aem context field Field aemContextField = getField(testClass, AemContext.class); if (aemContextField != null && Modifier.isStatic(aemContextField.getModifiers())) { state = true; } else { // check for static method with BeforeAll annotation Method method = getAnnotatedMethod(testClass, BeforeAll.class, AemContext.class); if (method != null && Modifier.isStatic(method.getModifiers())) { state = true; } } // cache state in extension store AemContextStore.storeBeforeAllState(extensionContext, state); } return state; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy