Assert.java
package uk.org.lidalia.test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import uk.org.lidalia.lang.Modifier;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.isA;
import static uk.org.lidalia.lang.Exceptions.throwUnchecked;
import static uk.org.lidalia.test.CombinableMatcher.both;
/**
* Utility Hamcrest matchers for unit test assertions.
*/
public final class Assert {
/**
* Asserts that a class is not instantiable - it exists only for its static members.
* <p>
* More precisely, asserts that the class:
* <ul>
* <li>Has Object as its immediate superclass</li>
* <li>Has only one constructor</li>
* <li>That constructor is private</li>
* <li>That constructor takes no arguments</li>
* <li>That constructor will throw an {@link UnsupportedOperationException} with message "Not Instantiable" if it is
* invoked via reflection.</li>
* </ul>
* <p>
* Usage:
* {@code assertThat(Values.class, isNotInstantiable());}
*
* @return a matcher that asserts that a class is not instantiable
*/
public static Matcher<Class<?>> isNotInstantiable() {
return both(aClassWhoseSuperClass(is(equalTo(Object.class))))
.and(aClassWhoseSetOfConstructors(both(
is(Assert.<List<Constructor<?>>>aCollectionWhoseSize(is(1))))
.and(is(Assert.<List<Constructor<?>>, Constructor<?>>aListWhoseElementAtIndex(0, both(
is(aConstructorWhoseParameterTypes(is(Assert.<List<Class<?>>>aCollectionWhoseSize(is(0))))))
.and(isAMemberWithModifier(Modifier.PRIVATE))
.and(aConstructorWhoseThrownException(both(
isA(UnsupportedOperationException.class))
.and(is(aThrowableWhoseMessage(is("Not instantiable")))))))))));
}
/**
* Facilitates making an assertion about the superclass of a {@link Class}.
* <p>
* Usage:
* {@code assertThat(String.class, is(aClassWhoseSuperClass(is(Object.class))));}
*
* @param classMatcher the matcher that will be applied to the class's superclass
* @param <U> the type of the superclass of the class being matched
* @param <T> the type of the class being matched
* @return a matcher that will assert something about the superclass of a class
*/
public static <U, T extends U> FeatureMatcher<Class<? extends T>, Class<? extends U>> aClassWhoseSuperClass(
final Matcher<? extends Class<? extends U>> classMatcher) {
return new FeatureMatcher<Class<? extends T>, Class<? extends U>>(
classMatcher, "a Class whose super class", "'s super class") {
@Override
protected Class<? extends U> featureValueOf(final Class<? extends T> actual) {
return (Class<? extends U>) actual.getSuperclass();
}
};
}
private static FeatureMatcher<Class<?>, List<Constructor<?>>> aClassWhoseSetOfConstructors(
final Matcher<List<Constructor<?>>> matcher) {
return new FeatureMatcher<Class<?>, List<Constructor<?>>>(
matcher, "a Class whose set of constructors", "'s constructors") {
@Override
protected List<Constructor<?>> featureValueOf(final Class<?> actual) {
final List<Constructor<?>> constructors = asList(actual.getDeclaredConstructors());
Collections.sort(constructors, new Comparator<Constructor<?>>() {
@Override
public int compare(final Constructor<?> one, final Constructor<?> other) {
return one.toString().compareTo(other.toString());
}
});
return constructors;
}
};
}
/**
* Facilitates making an assertion about the size of a {@link Collection}.
* <p>
* Usage:
* {@code assertThat(asList(1, 2, 3), is(aCollectionWhoseSize(is(3))));}
*
* @param sizeMatcher the matcher that will be applied to the collection's size
* @param <T> the type of the collection whose size will be matched
* @return a matcher that will assert something about a collection's size
*/
public static <T extends Collection<?>> Matcher<T> aCollectionWhoseSize(final Matcher<Integer> sizeMatcher) {
return new FeatureMatcher<T, Integer>(sizeMatcher, "a Collection whose size", "'s length") {
@Override
protected Integer featureValueOf(final T actual) {
return actual.size();
}
};
}
/**
* Facilitates making an assertion about the element at a given index of a {@link List}.
* <p>
* Usage:
* {@code assertThat(asList("a", "b", "c"), is(aListWhoseElementAtIndex(2, is("c"))));}
*
* @param index the index of the element in the list the matcher will be applied to
* @param matcher the matcher that will be applied to the element
* @param <T> the type of the List
* @param <E> the type of the elements in the List
* @return a matcher that will assert something about the element at the given index of a collection
*/
public static <T extends List<? extends E>, E> Matcher<T> aListWhoseElementAtIndex(
final Integer index, final Matcher<E> matcher) {
return new FeatureMatcher<T, E>(matcher, "a List whose element at index " + index, "'s element at index " + index) {
@Override
protected E featureValueOf(final T actual) {
if (actual.size() > index) {
return actual.get(index);
} else {
throw new AssertionError(actual + " has no element at index " + index);
}
}
};
}
/**
* Asserts that a given {@link Member} has a given {@link Modifier}.
* <p>
* Usage:
* {@code assertThat(Object.class.getMethod("toString"), isAMemberWithModifier(Modifier.PUBLIC));}
*
* @param modifier the modifier the member is expected to have
* @param <T> the type of the Member
* @return a matcher that will assert a member has a modifier
*/
public static <T extends Member> Matcher<T> isAMemberWithModifier(final Modifier modifier) {
return new TypeSafeDiagnosingMatcher<T>() {
@Override
protected boolean matchesSafely(final T item, final Description mismatchDescription) {
final boolean matches = modifier.existsOn(item);
if (!matches) {
mismatchDescription.appendValue(item).appendText(" did not have modifier ").appendValue(modifier);
}
return matches;
}
@Override
public void describeTo(final Description description) {
description.appendText("is a member with modifier " + modifier);
}
};
}
private static Matcher<Constructor<?>> aConstructorWhoseParameterTypes(final Matcher<List<Class<?>>> parameterMatcher) {
return new FeatureMatcher<Constructor<?>, List<Class<?>>>(
parameterMatcher, "a constructor whose parameter types", "'s parameter types") {
@Override
protected List<Class<?>> featureValueOf(final Constructor<?> actual) {
return asList(actual.getParameterTypes());
}
};
}
private static Matcher<Constructor<?>> aConstructorWhoseThrownException(final Matcher<? extends Throwable> throwableMatcher) {
return new FeatureMatcher<Constructor<?>, Throwable>(
throwableMatcher, "a constructor whose thrown exception", "'s thrown exception") {
@Override
protected Throwable featureValueOf(final Constructor<?> constructor) {
try {
constructor.setAccessible(true);
constructor.newInstance();
return null;
} catch (InvocationTargetException e) {
return e.getCause();
} catch (Exception e) {
return throwUnchecked(e, null);
} finally {
constructor.setAccessible(false);
}
}
};
}
private static Matcher<Throwable> aThrowableWhoseMessage(final Matcher<String> messageMatcher) {
return new FeatureMatcher<Throwable, String>(messageMatcher, "a throwable whose message", "'s message") {
@Override
protected String featureValueOf(final Throwable actual) {
return actual.getMessage();
}
};
}
private Assert() {
throw new UnsupportedOperationException("Not instantiable");
}
}