Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.idea/*
target/*
**/target/*
*.iml
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,33 @@ Add the following dependency to your `pom.xml` file:
usage
-----

`An example that asserts a trait against a subject:

```Java
class FunctionalIterableTest {

@Test
void laziness() {
assertTrait(Laziness.class, FunctionalIterable.iterable(1, 2, 3, 4));
}
}
```

This example is implemented in the style of Junit 5, but since the only integration with traitor necessary is a
static method call, this should be compatible with any Java test framework.

**junit 4 integration**

With Junit 4, traitor supports a `Runner`-based integration. To use it, modify the dependency on traitor:

```xml
<dependency>
<groupId>com.jnape.palatable</groupId>
<artifactId>traitor-junit4</artifactId>
<version>2.0.0</version>
</dependency>
```

An example test suite that tests traits alongside unit tests might look like:

```Java
Expand Down
39 changes: 39 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>com.jnape.palatable</groupId>
<artifactId>traitor-project</artifactId>
<version>1.4.1-SNAPSHOT</version>
</parent>

<artifactId>traitor</artifactId>
<version>1.4.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Traitor</name>
<description>
Trait testing with JUnit.
</description>

<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.jnape.palatable.traitor.assertion;

import com.jnape.palatable.traitor.internal.TraitInvoker;
import com.jnape.palatable.traitor.traits.Trait;

public final class AssertTrait {
private AssertTrait() {
}

public static void assertTrait(Class<? extends Trait> traitClass, Object subject) {
new TraitInvoker(traitClass).invokeExplosively(subject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.jnape.palatable.traitor.internal;

import com.jnape.palatable.traitor.traits.Trait;

import java.lang.reflect.Constructor;
import java.util.Objects;

import static java.lang.String.format;
import static java.util.Arrays.asList;

public final class TraitInvoker {

private final Class<? extends Trait> traitClass;
private final Iterable<?> subjects;

public TraitInvoker(Class<? extends Trait> traitClass, Iterable<?> subjects) {
this.traitClass = traitClass;
this.subjects = subjects;
}

public TraitInvoker(Class<? extends Trait> traitClass, Object... subjects) {
this(traitClass, asList(subjects));
}

public void invokeExplosively(Object... subjects) {
Trait trait = constructTrait(traitClass);
for (Object subject : subjects) {
trait.test(subject);
}
}

private static Trait constructTrait(Class<? extends Trait> traitClass) {
try {
Constructor<? extends Trait> traitConstructor = traitClass.getDeclaredConstructor();
traitConstructor.setAccessible(true);
return traitConstructor.newInstance();
} catch (ReflectiveOperationException e) {
throw new AssertionError("Unable to instantiate trait: " + traitClass.getSimpleName(), e);
}
}

public Iterable<?> getSubjects() {
return subjects;
}

public String getName() {
return format("Trait: %s", traitClass.getSimpleName());
}

@Override
public int hashCode() {
return Objects.hash(traitClass);
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
TraitInvoker that = (TraitInvoker) obj;
return Objects.equals(traitClass, that.traitClass);
}
}
38 changes: 38 additions & 0 deletions junit4/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>com.jnape.palatable</groupId>
<artifactId>traitor-project</artifactId>
<version>1.4.1-SNAPSHOT</version>
</parent>

<artifactId>traitor-junit4</artifactId>
<version>1.4.1-SNAPSHOT</version>

<name>Traitor Junit 4</name>
<description>
Trait testing integrated with JUnit 4.
</description>

<dependencies>
<dependency>
<groupId>com.jnape.palatable</groupId>
<artifactId>traitor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.jnape.palatable.traitor.framework;

import com.jnape.palatable.traitor.internal.TraitInvoker;
import com.jnape.palatable.traitor.traits.Trait;
import org.junit.runners.model.FrameworkMethod;

import java.lang.reflect.Method;
import java.util.stream.StreamSupport;

public class TraitFrameworkMethod extends FrameworkMethod {

private static final String TEST_METHOD_NAME = "test";
public static final Method TRAIT_TEST_METHOD = getTraitTestMethod();

private final TraitInvoker traitInvoker;
private final Subjects subjects;

public TraitFrameworkMethod(Class<? extends Trait> traitClass, Subjects subjects) {
super(TRAIT_TEST_METHOD);
this.subjects = subjects;
traitInvoker = new TraitInvoker(traitClass, subjects);
}

@Override
@SuppressWarnings("unchecked")
public Object invokeExplosively(Object target, Object... params) {
traitInvoker.invokeExplosively(StreamSupport.stream(subjects.spliterator(), false)
.toArray(Object[]::new));
return null;
}

@Override
public String getName() {
return traitInvoker.getName();
}

@Override
public boolean equals(Object other) {
return other instanceof TraitFrameworkMethod
&& super.equals(other)
&& traitInvoker.equals(((TraitFrameworkMethod) other).traitInvoker);
}

@Override
public String toString() {
return getName();
}

private static Method getTraitTestMethod() {
try {
return Trait.class.getDeclaredMethod(TEST_METHOD_NAME, Object.class);
} catch (NoSuchMethodException e) {
throw new AssertionError("The interface method must exist");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jnape.palatable.traitor.runners;

import com.jnape.palatable.traitor.annotations.TestTraits;
import com.jnape.palatable.traitor.framework.Subjects;
import com.jnape.palatable.traitor.framework.TraitFrameworkMethod;
import com.jnape.palatable.traitor.traits.Trait;
import org.junit.runners.BlockJUnit4ClassRunner;
Expand All @@ -13,6 +14,7 @@
import java.util.List;
import java.util.Set;

import static com.jnape.palatable.traitor.framework.Subjects.subjects;
import static java.util.Arrays.asList;

public class Traits extends BlockJUnit4ClassRunner {
Expand All @@ -36,20 +38,23 @@ private void addTraitTestingMethods(List<FrameworkMethod> testMethods) {
for (FrameworkMethod traitAnnotatedMethod : traitAnnotatedMethods)
try {
Object testSubject = traitAnnotatedMethod.invokeExplosively(createTest());
Subjects subjects = testSubject instanceof Subjects
? (Subjects<?>) testSubject
: subjects(testSubject);
Class<? extends Trait>[] traits = traitAnnotatedMethod.getAnnotation(TestTraits.class).value();
testMethods.addAll(synthesizeTraitFrameworkMethods(new LinkedList<>(asList(traits)), testSubject));
testMethods.addAll(synthesizeTraitFrameworkMethods(new LinkedList<>(asList(traits)), subjects));
} catch (Throwable t) {
t.printStackTrace();
throw new AssertionError("Couldn't create a test subject");
}
}

private static Set<TraitFrameworkMethod> synthesizeTraitFrameworkMethods(
LinkedList<Class<? extends Trait>> traitClasses, Object testSubject) {
LinkedList<Class<? extends Trait>> traitClasses, Subjects subjects) {
LinkedHashSet<TraitFrameworkMethod> traitFrameworkMethods = new LinkedHashSet<>();
while (!traitClasses.isEmpty()) {
Class<? extends Trait> traitClass = traitClasses.poll();
traitFrameworkMethods.add(TraitFrameworkMethod.synthesize(traitClass, testSubject));
traitFrameworkMethods.add(new TraitFrameworkMethod(traitClass, subjects));
TestTraits testTraits = traitClass.getAnnotation(TestTraits.class);
if (testTraits != null) {
traitClasses.addAll(0, asList(testTraits.value()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.jnape.palatable.traitor.framework;

import com.jnape.palatable.traitor.traits.Trait;
import org.junit.Test;

import static com.jnape.palatable.traitor.framework.Subjects.subjects;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;

public class TraitFrameworkMethodTest {

@Test
public void equalsOtherWithSameTraitAndSubjects() {
Subjects<Object> subjects = subjects(new Object());

TraitFrameworkMethod bar = new TraitFrameworkMethod(Bar.class, subjects);

assertThat(bar, is(new TraitFrameworkMethod(Bar.class, subjects)));
assertThat(bar, is(not(new TraitFrameworkMethod(Baz.class, subjects))));
}

@Test
public void worksWithInheritedTestMethod() throws Throwable {
TraitFrameworkMethod inherited = new TraitFrameworkMethod(Bar.class, subjects(new Object()));
assertEquals("Trait: Bar", inherited.getName());
inherited.invokeExplosively(new Object());
}

static abstract class Foo implements Trait<Object> {
@Override
public void test(Object o) {
}
}

static class Bar extends Foo {
}

static class Baz extends Foo {
}
}
Loading