28 Mar 03:27
Bug report (with fix) for version 1.0.1
From: Breidenbach, Kevin <Kevin.Breidenbach@...>
Subject: Bug report (with fix) for version 1.0.1
Newsgroups: gmane.comp.java.jmock.devel
Date: 2006-03-28 01:30:01 GMT
Subject: Bug report (with fix) for version 1.0.1
Newsgroups: gmane.comp.java.jmock.devel
Date: 2006-03-28 01:30:01 GMT
All,
I'd like to report a
bug (along with a fix). The bug is to do with creation of concrete class mock
objects. If the constructor of a class we wish to mock calls a non-private
method in that class, the callback from the cglib libraries that causes the
following exception:
Caused by:
org.jmock.core.DynamicMockError: mockDummy: no match found
Invoked: com.bofa.gmtt.jmocktest.classestomock.Dummy.init()
Allowed:
No expectations set
Invoked: com.bofa.gmtt.jmocktest.classestomock.Dummy.init()
Allowed:
No expectations set
I have attached a
source code with a unit test that will reproduce the bug. The full outline of
the bug is below.
While mocking
concrete classes is not optimal, and creating classes whose constructors call
non-private methods is not necessarily a best-practices, there are many
frameworks and libraries that developers need to mock that do have this
"feature". JMSTemplate in the Spring 1.2.x library is one of these such classes,
and without the fix it is impossible to mock that class.
The fix is simple
and involves placing a boolean semaphore around the creation of a CGLib proxy
that prevents invocation callbacks being acted upon until the class has been
created and returned to the test class.
The source
code is attached, as well as the binary. The source has been tested against the
unit and acceptance tests in the JMock 1.0.1 download package.
Thanks,
Kevin
Full
description of bug:
The jmock cglib
library allows mocking of concrete classes, however there is a bug in the
library that causes an error if the constructor of the class being mocked calls
a non-private method within that class. While the best solution for mocking is
to use interface/implementation separation, this is an important bug as
libraries we would want to mock have constructors that call protected methods
(e.g. JMSTemplate in Spring 1.2), which makes it impossible to mock out using
JMock. The failure actually occurs on creation of the mock object proxy, where
an error occurs stating that there the method call was not expected.
e.g.
Class
that we want to mock
package
com.bofa.gmtt.jmocktest.classestomock;
public class Dummy {
private boolean initialized = false;
public Dummy() {
init();
}
init();
}
//This will work if the protection level is increased to
private
protected void init() {
System.out.println("Creating Object");
initialized = true;
}
System.out.println("Creating Object");
initialized = true;
}
/**
* Pointless method just to give the object something to test with mocking.
* <at> return whether the object has been initialized.
*/
public boolean initialized() {
return initialized;
}
}
* Pointless method just to give the object something to test with mocking.
* <at> return whether the object has been initialized.
*/
public boolean initialized() {
return initialized;
}
}
Class
using the Dummy class
package
com.bofa.gmtt.jmocktest;
import
com.bofa.gmtt.jmocktest.classestomock.Dummy;
public class DummyUser {
private final Dummy dummy;
// dependency injected through
constructor
public DummyUser(Dummy dummy) {
this.dummy = dummy;
}
public DummyUser(Dummy dummy) {
this.dummy = dummy;
}
public boolean initialized() {
return dummy.initialized();
}
}
return dummy.initialized();
}
}
Unit test
attempting to mock out the Dummy class
package
com.bofa.gmtt.jmocktest;
import
com.bofa.gmtt.jmocktest.classestomock.Dummy;
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase;
public class
TestDummyUser extends MockObjectTestCase {
private DummyUser dummyUser;
private Mock dummyMock;
private Mock dummyMock;
public void setUp() throws Exception {
//
The following line produces the error unless the init method is changed to be a
private method
dummyMock = mock(Dummy.class); // see error line below
dummyUser = new DummyUser((Dummy) dummyMock.proxy());
}
dummyMock = mock(Dummy.class); // see error line below
dummyUser = new DummyUser((Dummy) dummyMock.proxy());
}
public void testInitialized() throws Exception
{
dummyMock.expects(once()).method("initialized").withNoArguments().will(returnValue(true));
assertTrue(dummyUser.initialized());
}
}
dummyMock.expects(once()).method("initialized").withNoArguments().will(returnValue(true));
assertTrue(dummyUser.initialized());
}
}
When run this gives
the following error, that while at first glances looks like a cglib error, it is
actually caused by jmock throwing a DynamicMockError exception - which shouldn't
be thrown for this type of error during proxy creation:
net.sf.cglib.core.CodeGenerationException: org.jmock.core.DynamicMockError-->mockDummy: no match found
Invoked: com.bofa.gmtt.jmocktest.classestomock.Dummy.init()
Allowed:
No expectations set
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:235)
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:220)
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:216)
at net.sf.cglib.proxy.Enhancer.createUsingReflection(Enhancer.java:640)
at net.sf.cglib.proxy.Enhancer.firstInstance(Enhancer.java:538)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:225)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:660)
at org.jmock.cglib.CGLIBCoreMock.<init>(Unknown Source)
at org.jmock.cglib.CGLIBCoreMock.<init>(Unknown Source)
at org.jmock.cglib.MockObjectTestCase.newCoreMock(Unknown Source)
at org.jmock.MockObjectTestCase.mock(Unknown Source)
at org.jmock.MockObjectTestCase.mock(Unknown Source)
at com.bofa.gmtt.jmocktest.TestDummyUser.setUp(TestDummyUser.java:16)
at org.jmock.core.VerifyingTestCase.runBare(Unknown Source)
at com.intellij.rt.execution.junit2.JUnitStarter.main(JUnitStarter.java:32)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
Caused by: org.jmock.core.DynamicMockError: mockDummy: no match found
Invoked: com.bofa.gmtt.jmocktest.classestomock.Dummy.init()
Allowed:
No expectations set
at org.jmock.core.AbstractDynamicMock.mockInvocation(Unknown Source)
at org.jmock.cglib.CGLIBCoreMock.intercept(Unknown Source)
at com.bofa.gmtt.jmocktest.classestomock.Dummy$$EnhancerByCGLIB$$3412a35b.init(<generated>)
at com.bofa.gmtt.jmocktest.classestomock.Dummy.<init>(Dummy.java:11)
at com.bofa.gmtt.jmocktest.classestomock.Dummy$$EnhancerByCGLIB$$3412a35b.<init>(<generated>)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:228)
... 34 more
Process finished with exit code -1
- Jörg
RSS Feed