Skip to content
This repository has been archived by the owner on Nov 23, 2021. It is now read-only.

Commit

Permalink
Merge pull request #264 from Cognifide/153-junit5-support
Browse files Browse the repository at this point in the history
153 junit5 support
  • Loading branch information
Shaihuludus committed Aug 3, 2018
2 parents dde37d9 + 446e99c commit 75affe3
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 1 deletion.
34 changes: 34 additions & 0 deletions bb-core/src/main/java/com/cognifide/qa/bb/RunWithJunit5.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*-
* #%L
* Bobcat
* %%
* Copyright (C) 2018 Cognifide Ltd.
* %%
* 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 com.cognifide.qa.bb;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface RunWithJunit5 {


}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
package com.cognifide.qa.bb.loadable.mapper;

import com.cognifide.qa.bb.RunWithJunit5;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
Expand Down Expand Up @@ -40,7 +41,7 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {

private <I> boolean isApplicable(Class<? super I> rawType) {
boolean result;
if (rawType.isAnnotationPresent(RunWith.class)
if ( (rawType.isAnnotationPresent(RunWith.class) || rawType.isAnnotationPresent(RunWithJunit5.class))
&& !rawType.isAnnotationPresent(CucumberOptions.class)) {
result = true;
} else {
Expand Down
68 changes: 68 additions & 0 deletions bb-junit5/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
#%L
Bobcat
%%
Copyright (C) 2018 Cognifide Ltd.
%%
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%
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>bobcat</artifactId>
<groupId>com.cognifide.qa.bb</groupId>
<version>1.4.1-SNAPSHOT</version>
</parent>

<artifactId>bb-junit5</artifactId>

<dependencies>

<dependency>
<groupId>com.cognifide.qa.bb</groupId>
<artifactId>bb-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>

<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
</dependency>

<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*-
* #%L
* Bobcat
* %%
* Copyright (C) 2018 Cognifide Ltd.
* %%
* 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 com.cognifide.qa.bb.junit5.guice;

import static java.util.stream.Collectors.toSet;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;

import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;

/**
* Extension that will start guice and is responsible for the injections to test instance Based on
* <a href="https://github.com/JeffreyFalgout/junit5-extensions/tree/master/guice-extension">Guice
* Extension</a>
*/
public class GuiceExtension implements TestInstancePostProcessor {

private static final Namespace NAMESPACE =
Namespace.create("com", "cognifide", "qa", "bb", "junit", "guice");

@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context)
throws Exception {

getOrCreateInjector(context).ifPresent(injector -> injector.injectMembers(testInstance));

}

/**
* Create {@link Injector} or get existing one from test context
*/
private static Optional<Injector> getOrCreateInjector(ExtensionContext context)
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {

Optional<AnnotatedElement> optionalAnnotatedElement = context.getElement();
if (!optionalAnnotatedElement.isPresent()) {
return Optional.empty();
}

AnnotatedElement element = optionalAnnotatedElement.get();
Store store = context.getStore(NAMESPACE);

Injector injector = store.get(element, Injector.class);
if (injector == null) {
injector = createInjector(context);
store.put(element, injector);
}

return Optional.of(injector);
}

/**
* Creates {@link Injector} from test context
*/
private static Injector createInjector(ExtensionContext context)
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Optional<Injector> parentInjector = getParentInjector(context);
List<? extends Module> modules = getNewModules(context);

return parentInjector
.map(injector -> injector.createChildInjector(modules))
.orElseGet(() -> Guice.createInjector(modules));
}

/**
* Retrieves {@link Injector} from parent test context
*/
private static Optional<Injector> getParentInjector(ExtensionContext context)
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
final Optional<ExtensionContext> optionalParent = context.getParent();
if (optionalParent.isPresent()) {
return getOrCreateInjector(optionalParent.get());
}
return Optional.empty();
}

/**
* Gets all new {@link Module} instances for injections
*/
private static List<? extends Module> getNewModules(ExtensionContext context)
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Set<Class<? extends Module>> moduleTypes = getNewModuleTypes(context);
List<Module> modules = new ArrayList<>(moduleTypes.size());
for (Class<? extends Module> moduleType : moduleTypes) {
Constructor<? extends Module> moduleCtor = moduleType.getDeclaredConstructor();
moduleCtor.setAccessible(true);

modules.add(moduleCtor.newInstance());
}

context.getElement().ifPresent(element -> {
if (element instanceof Class) {
modules.add(new AbstractModule() {
@Override
protected void configure() {
requestStaticInjection((Class<?>) element);
}
});
}
});

return modules;
}

/**
* Returns all new {@link Module} declared in current context (returns empty set if modules where
* already declared)
*/
private static Set<Class<? extends Module>> getNewModuleTypes(ExtensionContext context) {
Optional<AnnotatedElement> optionalAnnotatedElement = context.getElement();
if (!optionalAnnotatedElement.isPresent()) {
return Collections.emptySet();
}

Set<Class<? extends Module>> moduleTypes = getModuleTypes(optionalAnnotatedElement.get());
context.getParent()
.map(GuiceExtension::getContextModuleTypes)
.ifPresent(moduleTypes::removeAll);

return moduleTypes;
}

private static Set<Class<? extends Module>> getContextModuleTypes(ExtensionContext context) {
return getContextModuleTypes(Optional.of(context));
}

/**
* Returns module types that are present on the given context or any of its enclosing contexts.
*/
private static Set<Class<? extends Module>> getContextModuleTypes(
Optional<ExtensionContext> context) {

Set<Class<? extends Module>> contextModuleTypes = new LinkedHashSet<>();
while (context.isPresent() && (hasAnnotatedElement(context) || hasParent(context))) {
context
.flatMap(ExtensionContext::getElement)
.map(GuiceExtension::getModuleTypes)
.ifPresent(contextModuleTypes::addAll);
context = context.flatMap(ExtensionContext::getParent);
}

return contextModuleTypes;
}

private static boolean hasAnnotatedElement(Optional<ExtensionContext> context) {
return context.flatMap(ExtensionContext::getElement).isPresent();
}

private static boolean hasParent(Optional<ExtensionContext> context) {
return context.flatMap(ExtensionContext::getParent).isPresent();
}

private static Set<Class<? extends Module>> getModuleTypes(AnnotatedElement element) {

Optional<Class<? extends Module>[]> classes = findAnnotation(element, Modules.class)
.map(Modules::value);

if (classes.isPresent()) {
return Arrays.stream(classes.get()).collect(toSet());
}
return Sets.newHashSet();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*-
* #%L
* Bobcat
* %%
* Copyright (C) 2018 Cognifide Ltd.
* %%
* 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 com.cognifide.qa.bb.junit5.guice;


import com.google.inject.Injector;
import java.lang.reflect.AnnotatedElement;
import java.util.NoSuchElementException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;

/**
* Extensions helper class for retriving injector created in {@link GuiceExtension}
*/
public final class InjectorUtils {

private InjectorUtils() {
//for util class
}

/**
* Retrieves injector from the context store using Namespace. If it is not present it searches in
* parent context until it is found or the is end of hierachy
*
* @param context - extension context from junit5 extensions
* @param namespace - Namespace for current test invocation
* @return Injector or null if no injector is found
*/
public static Injector retrieveInjectorFromStore(ExtensionContext context, Namespace namespace) {
AnnotatedElement element = context.getElement()
.orElseThrow(() -> new NoSuchElementException("No element present"));

return context.getStore(namespace).getOrComputeIfAbsent(element,
absent -> retrieveInjectorFromStore(
context.getParent().orElseThrow(() -> new NoSuchElementException("No injector found")),
namespace), Injector.class);
}
}
Loading

0 comments on commit 75affe3

Please sign in to comment.