Since this question keeps coming up in the searches, and it's a bit outdated, I've decided to contribute with an answer.
As @Ogre Psalm33 and @bm212 have already said in their answer and comment, in order to generate a mock for a concrete class, we need to configure the Mockery
's Imposteriser
with a ClassImposteriser.INSTANCE
for versions of JMock prior to 2.11.0, while with a ByteBuddyClassImposteriser.INSTANCE
from JMock 2.11.0 on.
As of jmock 2.11.0, ClassImposteriser
has been deprecated in favor of the oddly-named ByteBuddyClassImposteriser
. https://github.com/jmock-developers/jmock-library/blob/master/jmock-legacy/src/main/java/org/jmock/lib/legacy/ClassImposteriser.java
However, the ClassImposteriser.INSTANCE
configuration (JMock < 2.11.0) works only up to JDK 15. While, the solution with ByteBuddyClassImposteriser.INSTANCE
(JMock >= 2.11.0) works for every JDK*, as long as the byte-buddy dependency is included when using JDKs 14 and above according to the comment section of the issue #204 in the official JMock's repository on GitHub.
However, despite what is reported in the issues of the official repository, from my tests emerged that when using ByteBuddyClassImposteriser.INSTANCE
without the byte-buddy dependency with JDKs 12 and 13, an IllegaleStateException
is thrown with the message "No code generation strategy available", making this dependency actually necessary from JDK 12 instead of 14.
*All my JMock tests have been conducted from JDK 8 to 21. At the moment of writing this answer, JDK 21 is the latest one.
Pre JMock 2.11.0
Supported up to JDK 15
Mockery context = new JUnit5Mockery() {
{
setImposteriser(ClassImposteriser.INSTANCE);
}
};
From JMock 2.11.0
Supported up to JDK 21 with byte-buddy dependency
Supported up to JDK 11 without byte-buddy dependency
Mockery context = new JUnit5Mockery() {
{
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
}
};
Example mocking the concrete class InputStream
The following example has been implemented using JDK 21, but can be reproduced with any other combination of JDKs and Mockery
configurations described above.
ConnectionFactory.java
public interface ConnectionFactory {
InputStream getData() throws Exception;
}
WebClient.java
public class WebClient {
public String getContent(ConnectionFactory connectionFactory) {
String workingContent;
StringBuffer content = new StringBuffer();
try (InputStream is = connectionFactory.getData()) {
int count;
while (-1 != (count = is.read())) {
content.append(new String(Character.toChars(count)));
}
workingContent = content.toString();
} catch (Exception e) {
workingContent = null;
}
return workingContent;
}
}
TestWebClientJMock.java
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.jmock.junit5.JUnit5Mockery;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class TestWebClientJMock {
@RegisterExtension
Mockery context = new JUnit5Mockery() {
{
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
}
};
@Test
public void testGetContentOk() throws Exception {
ConnectionFactory mockConnFactory = context.mock(ConnectionFactory.class);
InputStream mockInputStream = context.mock(InputStream.class);
context.checking(new Expectations() {
{
oneOf(mockConnFactory).getData();
will(returnValue(mockInputStream));
atLeast(1).of(mockInputStream).read();
will(onConsecutiveCalls(
returnValue(Integer.valueOf('W')),
returnValue(Integer.valueOf('o')),
returnValue(Integer.valueOf('r')),
returnValue(Integer.valueOf('k')),
returnValue(Integer.valueOf('s')),
returnValue(Integer.valueOf('!')),
returnValue(-1)));
oneOf(mockInputStream).close();
}
});
WebClient client = new WebClient();
String workingContent = client.getContent(mockConnFactory);
assertEquals("Works!", workingContent);
}
@Test
public void testGetContentCannotCloseInputStream() throws Exception {
ConnectionFactory mockConnFactory = context.mock(ConnectionFactory.class);
InputStream mockInputStream = context.mock(InputStream.class);
context.checking(new Expectations() {
{
oneOf(mockConnFactory).getData();
will(returnValue(mockInputStream));
oneOf(mockInputStream).read();
will(returnValue(-1));
oneOf(mockInputStream).close();
will(throwException(new IOException("cannot close")));
}
});
WebClient client = new WebClient();
String workingContent = client.getContent(mockConnFactory);
assertNull(workingContent);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>JMockTestClass</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock-junit5</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock-legacy</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.10</version>
</dependency>
</dependencies>
</project>