From: Nat P. <np...@us...> - 2002-10-11 14:57:46
|
Update of /cvsroot/mockobjects/nat/jmock/source/com/b13media/mock In directory usw-pr-cvs1:/tmp/cvs-serv19938/source/com/b13media/mock Modified Files: ExpectedCall.java Mock.java Added Files: Matches.java IsEventFrom.java Log Message: Mock does not care about order of method calls unless explicitly specified. Mock synthesises results for unexpected methods, unless explicitly told to throw errors. New predicates: IsEventFrom and Matches (regex matching of strings). --- NEW FILE: Matches.java --- /* Copyright (c) 2002 Nat Pryce. All rights reserved. * * Created on February 10, 2002, 11:35 PM */ package com.b13media.mock; import java.util.regex.Matcher; import java.util.regex.Pattern; /** Is the argument equal to another value, as tested by the * {@link java.lang.Object#equals} method? */ public class Matches implements Predicate { private Pattern _pattern; private Matcher _matcher; /** Creates a new instance of IsEqual */ public Matches( String regex ) { _pattern = Pattern.compile(regex); } public boolean eval( Object arg ) { if( arg instanceof String ) { Matcher matcher = _pattern.matcher((String)arg); return matcher.matches(); } else { return false; } } public String toString() { return "a string that matches <" + _pattern.toString() + ">"; } } --- NEW FILE: IsEventFrom.java --- /** Created on Jun 28, 2002 by npryce * Copyright (c) B13media Ltd. */ package com.b13media.mock; import java.util.EventObject; public class IsEventFrom implements Predicate { private Object _source; public IsEventFrom( Object source ) { _source = source; } public boolean eval( Object o ) { if( o instanceof EventObject ) { EventObject ev = (EventObject)o; return ev.getSource() == _source; } else { return false; } } public String toString() { return "an event from " + _source.toString(); } } Index: ExpectedCall.java =================================================================== RCS file: /cvsroot/mockobjects/nat/jmock/source/com/b13media/mock/ExpectedCall.java,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- ExpectedCall.java 22 May 2002 10:02:49 -0000 1.1.1.1 +++ ExpectedCall.java 11 Oct 2002 14:57:36 -0000 1.2 @@ -8,6 +8,7 @@ /** An expected call to a method of a mock object */ abstract class ExpectedCall + extends junit.framework.Assert { private String _name; private Predicate[] _expected_args; @@ -27,7 +28,7 @@ return _expected_args.length; } - public boolean checkArgument( int n, Object arg ) { + public boolean testArgument( int n, Object arg ) { return _expected_args[n].eval(arg); } Index: Mock.java =================================================================== RCS file: /cvsroot/mockobjects/nat/jmock/source/com/b13media/mock/Mock.java,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- Mock.java 22 May 2002 10:02:53 -0000 1.1.1.1 +++ Mock.java 11 Oct 2002 14:57:37 -0000 1.2 @@ -6,7 +6,17 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.util.LinkedList; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; + +import junit.framework.AssertionFailedError; /** A convenient class for creating simple @@ -14,18 +24,35 @@ */ public class Mock extends junit.framework.Assert - implements InvocationHandler + implements InvocationHandler { + /** A convenient constant for defining expectations for methods that + * have no methods. + */ public static final Predicate[] NO_ARGS = new Predicate[0]; private String _name; - private LinkedList _expectations = new LinkedList(); + private Map _expectations = new HashMap(); + private Map _order_constraints = new HashMap(); + private Set _called_methods = new HashSet(); + private boolean _strict = false; + + private Map _default_results = new HashMap(); /** Creates a named Mock object, The name will be included in the messages * of exceptions thrown to indicate violated expectations. */ public Mock( String name ) { _name = name; + addDefaultResult( byte.class, new Byte((byte)0) ); + addDefaultResult( short.class, new Short((short)0) ); + addDefaultResult( int.class, new Integer(0) ); + addDefaultResult( long.class, new Long(0L) ); + addDefaultResult( float.class, new Float(0.0f) ); + addDefaultResult( double.class, new Double(0.0d) ); + addDefaultResult( boolean.class, Boolean.FALSE ); + addDefaultResult( char.class, new Character('\0') ); + addDefaultResult( String.class, "" ); } /** Creates a Mock object and automatically assigns a name to it. The assigned @@ -35,13 +62,33 @@ * objects if you use more than one in the same test case. */ public Mock() { + this(null); _name = super.toString(); } + /** Returns the Mock's name. + */ public String toString() { return _name; } + /** Is the mock in strict mode? In strict mode the mock will throw + * {@link junit.framework.AssertionFailedError} exceptions when + * unexpected method calls are made. Otherwise, the mock ignore + * the call or return default arguments. + */ + public boolean isStrict() { + return _strict; + } + + public void setStrict( boolean strict ) { + _strict = strict; + } + + public void addDefaultResult( Class result_type, Object result_value ) { + _default_results.put( result_type, result_value ); + } + /** Expect a method call and mock the behaviour of that call. * * @param call @@ -50,7 +97,7 @@ * The state of the mock object after the call as occurred. */ public void expect( ExpectedCall call ) { - _expectations.addLast(call); + _expectations.put( call.getMethodName(), call ); } /** Expect a method call and return a result when it is called. @@ -106,47 +153,92 @@ expect( new ExpectedThrow( method, args, exception ) ); } + /** Define an order between two calls. The method named + * <var>subsequent_method</var> <em>must</em> be called after + * the method namd <var>preceding_method</var>, otherwise an + * {@link junit.framework.AssertionFailedError} will be thrown. + */ + public void order( String preceding_method, String subsequent_method ) { + Set preceding_calls; + if( _order_constraints.containsKey(subsequent_method) ) { + preceding_calls = (Set)_order_constraints.get(subsequent_method); + } else { + preceding_calls = new HashSet(); + _order_constraints.put( subsequent_method, preceding_calls ); + } + + preceding_calls.add( preceding_method ); + } + /** Called by the {@link java.lang.reflect.Proxy} to mock the behaviour of an * invoked method and check expectations. */ public Object invoke( Object obj, Method method, Object[] args ) throws Throwable { + _called_methods.add( method.getName() ); + try { return getClass().getMethod( method.getName(), method.getParameterTypes() ).invoke( this, args ); } catch( NoSuchMethodException ex ) { - return mockCall( method.getName(), args ); + return mockCall( method, args ); } } - protected Object mockCall( String method_name, Object[] args ) + protected Object mockCall( Method method, Object[] args ) throws Throwable { - assertTrue( _name + ": unexpected call to " + method_name, - !_expectations.isEmpty() ); - - ExpectedCall expected = (ExpectedCall)_expectations.removeFirst(); - - assertEquals( _name + ": unexpected method called", - expected.getMethodName(), method_name ); + String method_name = method.getName(); + if( _expectations.containsKey( method_name ) ) { + ExpectedCall expected = + (ExpectedCall)_expectations.get(method_name); + + checkCallOrder( method_name ); + checkArguments( expected, args ); + return expected.eval( args ); + + } else if( _strict ) { + throw new AssertionFailedError( + _name + ": unexpected call to " + method_name ); + } else { + return defaultResult( method.getReturnType() ); + } + } + + private void checkCallOrder( String method_name ) { + if( _order_constraints.containsKey(method_name) ) { + assertMethodsHaveBeenCalled( + (Set)_order_constraints.get(method_name) ); + } + } + + private void checkArguments( ExpectedCall expected, Object[] args ) + { int arg_count = (args == null) ? 0 : args.length; assertEquals( _name + ": wrong number of arguments to " + - method_name + " method", + expected.getMethodName() + " method", expected.getArgumentCount(), arg_count ); for( int i = 0; i < arg_count; i++ ) { - if( !expected.checkArgument( i, args[i] ) ) { + if( !expected.testArgument( i, args[i] ) ) { fail( _name + ": unexpected argument " + (i+1) + + " to " + expected.getMethodName() + " method" + ", expected " + expected.describeArgument(i) + ", was " + args[i].toString() ); } } - - return expected.eval( args ); + } + + private Object defaultResult( Class return_type ) { + if( _default_results.containsKey(return_type) ) { + return _default_results.get(return_type); + } else { + return null; + } } /** Fails if not all the expected calls have been made to this mock object. @@ -155,7 +247,39 @@ * Not all expected calls were made to this mock object. */ public void verify() { - assertTrue( "not all expected calls were made to " + _name, - _expectations.isEmpty() ); + assertAllExpectedMethodsCalled(); + } + + private void assertAllExpectedMethodsCalled() { + + assertMethodsHaveBeenCalled( _expectations.keySet() ); + } + + private void assertMethodsHaveBeenCalled( Set method_names ) { + Iterator i = method_names.iterator(); + while( i.hasNext() ) { + assertHasBeenCalled( (String)i.next() ); + } + } + + private void assertHasBeenCalled( String method_name ) { + if( !_called_methods.contains(method_name) ) { + fail( "method " + method_name + " was not called" ); + } + } + + public Object createInterface( Class interface_class ) { + return createInterface( interface_class.getClassLoader(), + new Class[]{ interface_class } ); + } + + public Object createInterface( ClassLoader loader, Class interface_class ) { + return createInterface( loader, new Class[]{ interface_class } ); + } + + public Object createInterface( ClassLoader loader, + Class[] interface_classes ) + { + return Proxy.newProxyInstance( loader, interface_classes, this ); } } |