JMock for TestNG (or JUnit-free JMock)

| | bookmark | email | 8 comments
JMock for TestNG Last days I have created a small utility class that would allow me to work with JMock without depending on JUnit. If you develop tests using TestNG and you need mocks a la JMock, than here it is (I have removed the imports so that the listing doesn't go too long):
/**
 * An utility class to allow static usage of JMock (removes the restriction
 * to subclass JMock specific classes).
 * 
 * @author Alexandru Popescu
 */
public class JMock {
    static final Constraint ANYTHING = new IsAnything();
    private static final WeakHashMap<Thread, List<Verifiable>> s_mockObjects = 
            new WeakHashMap<Thread, List<Verifiable>>();
    
    /**
     * Creates a mock object that mocks the given type.  
     * The mock object is named after the type;  the exact
     * name is calculated by {@link #defaultMockNameForType}.
     *
     * @param mockedType The type to be mocked.
     * @return A {@link Mock} object that mocks <var>mockedType</var>.
     */
    public static Mock mock(Class mockedType) {
        return mock(mockedType, defaultMockNameForType(mockedType));
    }

    /**
     * Creates a mock object that mocks the given type and is explicitly given a name.
     * The mock object is named after the type;  
     * the exact name is calculated by {@link #defaultMockNameForType}.
     *
     * @param mockedType The type to be mocked.
     * @param roleName The name of the mock object
     * @return A {@link Mock} object that mocks <var>mockedType</var>.
     */
    public static Mock mock( Class mockedType, String roleName ) {
        Mock newMock = new Mock(newCoreMock(mockedType, roleName));
        registerToVerify(newMock);
        return newMock;
    }
    
    public static Mock mock(Class mockedClass, 
                            String roleName, 
                            Class[] constructorArgumentTypes, 
                            Object[] constructorArguments) {
        Mock newMock = new Mock(newClassCoreMock(mockedClass, 
                                                 roleName, 
                                                 constructorArgumentTypes, 
                                                 constructorArguments));
        registerToVerify(newMock);
        return newMock;
    }

    public static Mock mock(Class mockedClass, 
                            Class[] constructorArgumentTypes, 
                            Object[] constructorArguments) {
        return mock(mockedClass, 
                    defaultMockNameForType(mockedClass), 
                    constructorArgumentTypes, 
                    constructorArguments);
    }

    public static Stub returnValue(Object o) {
        return new ReturnStub(o);
    }

    public static Stub returnValue(boolean result ) {
        return returnValue(new Boolean(result));
    }

    public static Stub returnValue(byte result ) {
        return returnValue(new Byte(result));
    }

    public static Stub returnValue(char result ) {
        return returnValue(new Character(result));
    }

    public static Stub returnValue(short result ) {
        return returnValue(new Short(result));
    }

    public static Stub returnValue(int result ) {
        return returnValue(new Integer(result));
    }

    public static Stub returnValue(long result ) {
        return returnValue(new Long(result));
    }

    public static Stub returnValue(float result ) {
        return returnValue(new Float(result));
    }

    public static Stub returnValue(double result ) {
        return returnValue(new Double(result));
    }

    public static Stub returnIterator(Collection collection) {
        return new ReturnIteratorStub(collection);
    }
    
    public static Stub returnIterator(Object[] array) {
        return new ReturnIteratorStub(array);
    }
    
    public static Stub throwException(Throwable throwable) {
        return new ThrowStub(throwable);
    }

    public static InvocationMatcher once() {
        return new InvokeOnceMatcher();
    }

    public static InvocationMatcher atLeastOnce() {
        return new InvokeAtLeastOnceMatcher();
    }

    public static InvocationMatcher exactly(int expectedCount) {
        return new InvokeCountMatcher(expectedCount);
    }
    
    public static InvocationMatcher never() {
        return new TestFailureMatcher("not expected");
    }

    public static InvocationMatcher never( String errorMessage ) {
        return new TestFailureMatcher("not expected ("+errorMessage+")");
    }
    
    public static Stub onConsecutiveCalls(Stub... stubs) {
        return new StubSequence(stubs);
    }
    
    public static Stub doAll(Stub... stubs) {
        return new DoAllStub(stubs);
    }
    
    public static IsEqual eq(Object operand) {
        return new IsEqual(operand);
    }

    public static IsEqual eq(boolean operand ) {
        return eq(new Boolean(operand));
    }

    public static IsEqual eq(byte operand ) {
        return eq(new Byte(operand));
    }

    public static IsEqual eq(short operand ) {
        return eq(new Short(operand));
    }

    public static IsEqual eq(char operand ) {
        return eq(new Character(operand));
    }

    public static IsEqual eq(int operand ) {
        return eq(new Integer(operand));
    }

    public static IsEqual eq(long operand ) {
        return eq(new Long(operand));
    }

    public static IsEqual eq(float operand ) {
        return eq(new Float(operand));
    }

    public static IsEqual eq(double operand ) {
        return eq(new Double(operand));
    }

    public static IsCloseTo eq( double operand, double error ) {
        return new IsCloseTo(operand, error);
    }

    public static IsSame same( Object operand ) {
        return new IsSame(operand);
    }

    public static IsInstanceOf isA( Class operandClass ) {
        return new IsInstanceOf(operandClass);
    }

    public static StringContains stringContains(String substring) {
        return new StringContains(substring);
    }

    public static StringContains contains(String substring) {
        return stringContains(substring);
    }
    
    public static StringStartsWith startsWith(String substring ) {
        return new StringStartsWith(substring);
    }
    
    public static StringEndsWith endsWith(String substring ) {
        return new StringEndsWith(substring);
    }
    
    public static IsNot not(Constraint c ) {
        return new IsNot(c);
    }

    public static And and(Constraint left, Constraint right ) {
        return new And(left, right);
    }

    public static Or or(Constraint left, Constraint right ) {
        return new Or(left, right);
    }

    /// HINT: not sure about these following 3
    public static Object newDummy( Class dummyType ) {
        return Dummy.newDummy(dummyType);
    }

    /// HINT: what is this?
    public static Object newDummy( Class dummyType, String name ) {
        return Dummy.newDummy(dummyType, name);
    }

    /// HINT: what is this?
    public static Object newDummy( String name ) {
        return Dummy.newDummy(name);
    }
    
    public static void assertThat(Object actual, Constraint constraint) {
        if (!constraint.eval(actual)) {
            StringBuffer message = new StringBuffer("\nExpected: ");
            constraint.describeTo(message);
            message.append("\n    got : ").append(actual).append('\n');
            throw new AssertionError(message);
        }
    }

    public static void assertThat(boolean actual, Constraint constraint) {
        assertThat(new Boolean(actual), constraint);
    }

    public static void assertThat(byte actual, Constraint constraint) {
        assertThat(new Byte(actual), constraint);
    }

    public static void assertThat(short actual, Constraint constraint) {
        assertThat(new Short(actual), constraint);
    }

    public static void assertThat(char actual, Constraint constraint) {
        assertThat(new Character(actual), constraint);
    }

    public static void assertThat(int actual, Constraint constraint) {
        assertThat(new Integer(actual), constraint);
    }

    public static void assertThat(long actual, Constraint constraint) {
        assertThat(new Long(actual), constraint);
    }

    public static void assertThat(float actual, Constraint constraint) {
        assertThat(new Float(actual), constraint);
    }

    public static void assertThat(double actual, Constraint constraint) {
        assertThat(new Double(actual), constraint);
    }
    
    public static HasPropertyWithValue hasProperty(String propertyName, Constraint expectation) {
        return new HasPropertyWithValue(propertyName, expectation);
    }
    
    public static HasProperty hasProperty(String propertyName) {
       return new HasProperty(propertyName);
    }
    
    public static HasToString toString(Constraint toStringConstraint) {
        return new HasToString(toStringConstraint);
    }
    
    public static IsCompatibleType compatibleType(Class baseType) {
        return new IsCompatibleType(baseType);
    }
    
    public static IsIn isIn(Collection collection) {
        return new IsIn(collection);
    }
    
    public static IsIn isIn(Object[] array) {
        return new IsIn(array);
    }
    
    public static IsCollectionContaining collectionContaining(Constraint elementConstraint) {
        return new IsCollectionContaining(elementConstraint);
    }
    
    public static IsCollectionContaining collectionContaining(Object element) {
        return collectionContaining(eq(element));
    }
    
    public static IsArrayContaining arrayContaining(Constraint elementConstraint) {
        return new IsArrayContaining(elementConstraint);
    }

    public static IsArrayContaining arrayContaining(Object element) {
        return arrayContaining(eq(element));
    }

    public static IsArrayContaining arrayContaining(boolean element) {
        return arrayContaining(new Boolean(element));
    }

    public static IsArrayContaining arrayContaining(byte element) {
        return arrayContaining(new Byte(element));
    }

    public static IsArrayContaining arrayContaining(short element) {
        return arrayContaining(new Short(element));
    }

    public static IsArrayContaining arrayContaining(char element) {
        return arrayContaining(new Character(element));
    }

    public static IsArrayContaining arrayContaining(int element) {
        return arrayContaining(new Integer(element));
    }

    public static IsArrayContaining arrayContaining(long element) {
        return arrayContaining(new Long(element));
    }

    public static IsArrayContaining arrayContaining(float element) {
        return arrayContaining(new Float(element));
    }

    public static IsArrayContaining arrayContaining(double element) {
        return arrayContaining(new Double(element));
    }
    
    public static IsMapContaining mapContaining(Constraint keyConstraint, Constraint valueConstraint) {
        return new IsMapContaining(keyConstraint, valueConstraint);
    }
    
    public static IsMapContaining mapContaining(Object key, Object value) {
        return mapContaining(eq(key), eq(value));
    }

    public static IsMapContaining mapWithKey(Object key) {
        return mapWithKey(eq(key));
    }

    public static IsMapContaining mapWithKey(Constraint keyConstraint) {
        return new IsMapContaining(keyConstraint, ANYTHING);
    }

    public static IsMapContaining mapWithValue(Object value) {
        return mapWithValue(eq(value));
    }

    public static IsMapContaining mapWithValue(Constraint valueConstraint) {
        return new IsMapContaining(ANYTHING, valueConstraint);
    }

    /**
     * Verify the expected behavior for the mock registered by the current thread.
     */
    public static void verify() {
        List<Verifiable> mocks = s_mockObjects.get(Thread.currentThread());
        if(null != mocks) {
            for(Verifiable verifiable : mocks) {
                verifiable.verify();
            }
        }
    }
    
    /**
     * Verify the expected behavior for the mocks defined as fields of the arguement object.
     * 
     * @param object the object to be inspected
     */
    public static void verifyObject(Object object) {
        Verifier.verifyObject(object);
    }

    /**
     * Helper method that delegates to {@link #verify()} and {@link verifyObject(Object)}.
     */
    public static void verifyAll(Object object) {
        verify();
        verifyObject(object);
    }
    
    /**
     * Verify the expected behavior for the mocks registered by the current thread and
     * also releases them.
     */
    public static synchronized void verifyAndRelease() {
        Thread currentThread= Thread.currentThread();
        List<Verifiable> mocks = s_mockObjects.get(currentThread);
        if(null != mocks) {
            for(Verifiable verifiable : mocks) {
                verifiable.verify();
            }
        }
        mocks.clear();
        s_mockObjects.put(currentThread, null);
    }
    
    /**
     * Helper method delegating to {@link #verifyAndRelease()} and {@link #verifyObject(Object)}.
     */
    public static void verifyAllAndRelese(Object object) {
        verifyAndRelease();
        verifyObject(object);
    }
    
    ///
    private static synchronized void registerToVerify(Verifiable verifiable) {
        List<Verifiable> mocks = s_mockObjects.get(Thread.currentThread());
        if(null == mocks) {
            mocks = new ArrayList<Verifiable>();
            s_mockObjects.put(Thread.currentThread(), mocks);
        }
        
        mocks.add(verifiable);
    }

    private static DynamicMock newCoreMock(Class mockedType, String roleName ) {
        return new CoreMock(mockedType, roleName);
    }

    private static DynamicMock newClassCoreMock(Class mockedClass, 
                                                String roleName,
                                                Class[] constructorArgumentTypes, 
                                                Object[] constructorArguments) {
        return new CGLIBCoreMock(mockedClass, roleName, constructorArgumentTypes, constructorArguments);
    }

    /**
     * Calculates a default role name for a mocked type.
     * @param mockedType
     * @return
     */
    private static String defaultMockNameForType(Class mockedType) {
        return "mock" + Formatting.classShortName(mockedType);
    }
}
It works for mocking both interfaces and classes (through CGLIB).
And here is a short example of usage (JDK5, because I use static imports):
public class TypeAccessFilterTest {
    @Test
    public void jmockBotNormalRequest() throws IOException, ServletException {
        // static imports
        Mock mockBotChecker = mock(BotChecker.class, new Class[0], new Object[0]);
        Mock mockFilterChain= mock(FilterChain.class);
        TypeAccessFilter taf= new TypeAccessFilter();
        taf.setBotChecker((BotChecker) mockBotChecker.proxy());
        
        mockFilterChain.expects(once()).method("doFilter")
                       .with(isA(HttpServletRequest.class),
                             isA(JSessionidBeautifierHttpResponseWrapper.class));
        
        taf.doFilter(m_mockRequest, m_mockResponse, (FilterChain) mockFilterChain.proxy());
        
        verifyAndRelease();
    }
As far as I know the latest versions of EasyMock allow the same JUnit-free usage, which in my opinion is a great thing.