From: Thomas C. <ald...@ne...> - 2001-08-15 19:18:59
|
Vincent "I like the dynamic proxy because it enables to skip the static mock implementation generation step. Even if you don't have an interface you can still extend a class." The implementation of java.lang.reflect.Proxy requires an interface - you cannot extend an implementation class with Proxy. However, you can override methods of an implementation class (in any JDK, AFAIK), with the anonymous inner class testing pattern, but you cannot add callable methods to the class' interface with an *anonymous* inner class. You can also use this latter pattern to extend and test interfaces prior to JDK 1.3 *if and only if* a compilable mock implementation exists, though this mock implementation can simply return null or do nothing for all methods as return values require (thus relatively easy to generate). "public void testSomething() { MockYYY mock = new MockYYY(); mock.setActualValueXXX("text/html"); mock.setActualValueZZZ(true); ... // set the expectations on the mock object (if need be) mock.setExpectedValueAAA("something"); ... // the method to unit test that calls XXX // and ZZZ as part of it's logic String result = myObject.someMethod(); ... // then the asserts, which can be a mix of junit asserts and // verifying against the expectations set on the mock object assertEquals("...", result); mock.verify(); }" If I understand this correctly, this is a good spot to do a side-by-side comparison of pieces of the test above with the patterns. One way, closest to that in the last version of the article draft I posted here: public void testSomething() { MockYYY mock = new MockYYY() { // Implements a verifiable interface private boolean called; String XXX() { return "text/html"); } boolean ZZZ() { return true; } void AAA(String someString) { assertEquals("something", someString); called = true; } boolean verify() { return called; } }; String result = myObject.someMethod(); assertEquals("...", result); assert(mock.verify()); } However, this is more efficient and renders the Verifiable interface unnecessary: public void testSomething() { MockYYY mock = new MockYYY() { String XXX() { return "text/html"); } boolean ZZZ() { return true; } void AAA(String someString) { assertEquals("something", something); } }; String result = myObject.someMethod(); assertEquals("...", result); } Unfortunately, this does not answer the question of, "What happens if method AAA is never called, but the call affects the state of object yyy and is required by contract?" If essential, we can use a small helper class to count calls: public class CallCount() { int required; public CallCount(int required) { this.required = required; } public void add() { required--; } public boolean check() { return required == 0; } } And use it thus: public void testSomething() { final CallCount calls = new CallCount(1); MockYYY mock = new MockYYY() { String XXX() { return "text/html"); } boolean ZZZ() { return true; } void AAA(String someString) { assertEquals("something", something); calls.add(); } }; String result = myObject.someMethod(); assertEquals("...", result); assert(calls.check()); } There still remains a slight chance, where there are two or more methods to call in the mock object and at least one of those methods is supposed to get called more than once, that the test might not catch the *incorrect* method call frequency. In my experience, this is fairly rare except for writing to output streams where a count of the number of lines or number of times printed is not generally useful. Moreover, calling the same method of another object, aside from some basic system classes, more than once in the same method is a symptom of poor method design. However, if we really must be sure, a slightly more advanced call counter could suffice. To prepare: CallCountMap calls = new CallCountMap(); calls.set("AAA", 6); calls.set("BBB", 4); To record in an anonymous class method: calls.add("AAA"); To check: assert(calls.check()); Of, if call the check method is implemented to cause an assertion failure, the additional assert is not necessary: calls.check(); Thus, without the call counters, you can make use of a Verifiable-like interface. With the call counters, you can put aside Verifiable completely while making most of your assertions directly in methods of the anonymous class. Now, there are some subtle points regarding "expectation folding" that I am waiting to hear back from Tim and Steve on to see if they have seen situations where this breaks down, but it is working for me thus far and causing some refactoring of production classes that proves useful later on. Also, if there *are* complex expectations, you can always replace the CallCountMap in the above with an expectation monitor class perhaps build on some of the expectation classes currently available in the mock objects project. "We probably need to work on a real life example" Indeed. I shall stay tuned. "The idea is simply that with the above described generic mechanism there is no need to inner classes at all." Agreed in theory with the generic mechanism you described, but it seems to me that the generic mechanism is not trivial, requiring a lot of code and a relatively large library to pull off as well as the prospect of complicated code generation and compilation. Tom |