Vincent
"With MO, the problem is for test writer to have to
implement a mock container (or some parts of it)."
I disagree with the assumption that test writers have to write a mock
container to do MO. I think we can accomplish fully functional unit testing
with *patterns* rather than frameworks or mocked containers. To wit, I am in
the midst of writing an article on mock object testing with patterns that
extends some ideas found here and at the mock objects project on SF with a
new twist. I am attaching an early draft here with pseudo-code, if you are
interested. I am successfully using some of the patterns in a fairly complex
project currently. (Feedback is welcome and Alex, if you are reading this,
could this help with your current EJB work?)
The short of the matter is that I do not think we need to create a mock
container to do unit testing with servlets. Now, integration testing is
another matter, and that is where Cactus' in-container testing really comes
to the fore. Although it is my preference that unit testing be separate from
integration testing both in code and management, I think there are some
solutions in your vote proposal that use method name space or external
configuration to identify the methodology of different tests (mock versus
in-container) that still allows the use of these patterns if it is essential
that Cactus manage everything to do with testing.
"a good MO implementation would not return null or do
nothing but rather would raise a runtime exception ... (like a
NotImplementedException)."
Very smart! I like it so much I added it to the draft patterns before
attaching it here.
"What you are saying in essence is "leave the choice open for the test
writer
instead of forcing him/her to use a given solution". This is fine but then
what would happen when you run your test in IC mode. What would cactus do
about the WebXmlQuery ? "
Although this is probably moot at this point, WebXmlQuery or
MockServletConfig would just serve as a utility to mock classes that need to
get some information from web.xml. Or, thinking of it another way, perhaps
there is no place for web.xml when testing with mock objects since web.xml
is fundamentally a container crutch?
Tom
author: Thomas Calivera
date: 2001-07-24
GOAL
Describe general testing patterns useful across the project.
CONCLUSION
When unit testing any one method or class, the goal is to make sure the method
calls the proper methods of other objects and makes a correct return. If mock
objects are passed in, it makes sense to do the verification of the method calls
within the mock objects themselves. To satisfy the compiler and interface
requirements, the following is useful:
boolean verify()
In every production class to facilitate testing by overriding the verify method
on a per-test basis. However, it is desirable to avoid adding this method to the
production class itself. As a compromise, it is possible to implement a general
inner class:
class MockSomeObject extends MockObject {
boolean verify() {
return false;
}
}
The tests can then use this mock class as the basis by overriding the
verification method using anonymous classes on a per-test basis. For example:
public void testSomething() {
boolean verified;
TestSubject subject;
MockSomeObject mock;
mock = new MockSomeObject() {
private boolean properArgument;
void someMethod(String argument) {
properArgument = argument.equals("hello");
}
boolean verify() {
return properArgument;
}
};
subject = new TestSubject(mock);
subject.doSomething();
verified = mock.verify();
assert(verified);
}
The scope of the class is best considered in the context of the range of tests
it applies to. If just one test, it properly belongs in the scope of the test's
method. If used by several tests within the same testing class, it belongs in
the scope of the testing class. If used by tests in different test classes, it
is best as an external class. In the latter two cases, it makes sense to include
only those methods that are identically overridden by two or more discrete tests
(individual tests needing a different implementation than what is implemented
here can further override the respective methods as necessary).
It is even more desirable to not even declare a mock inner class to simply
extend the base class to mock with the verify method. This is possible in the
case where interfaces exist, at which point a general interface:
public interface MockObject {
boolean verify();
}
Is useful when creating an instance of the Proxy class by passing it in with the
interface to create a mock object for. Then, the verification logic is
implemented by overriding the invoke() method of an invocation handler in an
anonymous inner class:
InvocationHandler handler = new InvocationHandler() {
private boolean properArgument;
public Object invoke(Object proxy, Method method, Object[] args) {
String methodName;
methodName = method.getName();
if (methodName.equals("someMethod")) {
String argument;
argument = (String) args[0];
properArgument = argument.equals("hello");
return null;
}
if (methodName.equals("verify")) {
return properArgument;
}
throw new NotImplementedException();
}
};
To actually create a viable mock object for which calls are processed through
the newly implemented handler, it is necessary to create the proxied object
using the java.reflect.Proxy class and passing the handler as well as the
requisite interface classes in as arguments:
mock = (SomeInterface) Proxy.newProxyInstance(
SomeInterface.class.getClassLoader(), new Class[]
{SomeInterface.class, MockObject.class}, handler);
At this point, the actual testing code is exactly as seen above with the
overriding of non-abstract classes:
subject = new TestSubject(mock);
subject.doSomething();
verified = mock.verify();
assert(verified);
In this latter case, there is no need for an actual external mock implementation
class for testing unless it makes sense from a scope perspective. Even then, it
is not necessary to create minimum implementation methods for all methods of an
interface.
NOTES
The use of the java.reflect.Proxy class assumes the use of JDK 1.3 or higher.
~~
In the case of classes with no default constructor, extending them in a mock
inner class is tricky because of the lack of a default constructor, which is
essential to Java inheritance. In general, a mock inner class is not concerned
with state introduced through its constructor since it is only concerned with
intercepting method calls from the test subject and providing any necessary
responses. Therefore, in the case of a base class of a mock class such as:
class SomeClass {
SomeClass(String someString) {
}
}
The mock class can extend it with the following constructor to meet compilation
requirements:
class MockSomeClass extends SomeClass {
MockSomeClass() {
super(null);
}
}
The call to super with the proper parameter signature satisfies the compiler
requirements. If passing a null to the base class constructor results in an NPE,
investigate creating a dummy object of the required class.
RESOURCES
* "Endo-Testing: Unit Testing with Mock Objects,"
http://sourceforge.net/docman/display_doc.php?docid=5585&group_id=18189
* Massol, Vince. Cactus Users Mailing List, [EMAIL PROTECTED]