ClassMock

Test Tool for Metadata-Based and Reflection-Based Components

ClassMock is a framework that helps the creation of unit tests for components that use reflection or annotations. In this kind of classes, the behavior is dependent of the class structure. This way, each test case usually works with a different class created specifically for the test. With ClassMock is possible to define and generate classes in runtime, allowing a better test readability and logic sharing between tests.

Download ClassMock

Sourceforge Project Page

ClassMock Forum

Traditional Reflection Test Cases

In traditional test cases for components that use reflection, there are classes defined in the test class or in the test method to be used. If the classes had some differences, the only way to reuse this definition code is inheritance. Bellow there is a test class for a component that gets a Java Bean and return a map with its properties:

package net.sf.classmock.example.property;

import static net.sf.classmock.ClassMockUtils.*;


import java.util.Map;

import net.sf.classmock.ClassMock;
import net.sf.classmock.Location;

import org.junit.Assert;
import org.junit.Test;

public class PropertyMapFactoryTest {

@Test
public void mapCreation(){

class Example{
private String prop1;
private int prop2;
public String getProp1() {
return prop1;
}
public void setProp1(String prop1) {
this.prop1 = prop1;
}
public int getProp2() {
return prop2;
}
public void setProp2(int prop2) {
this.prop2 = prop2;
}
}

Example example = new Example();
example.setProp1("test");
example.setProp2(13);

Map<String, String> map = PropertyMapFactory.getPropertyMap(example);

Assert.assertEquals("test", map.get("prop1"));
Assert.assertEquals("13", map.get("prop2"));
}


@Test
public void mapCreationWithIgnore(){

class Example{
private String prop1;
private int prop2;
public String getProp1() {
return prop1;
}
public void setProp1(String prop1) {
this.prop1 = prop1;
}
@Ignore
public int getProp2() {
return prop2;
}
public void setProp2(int prop2) {
this.prop2 = prop2;
}
}

Example example = new Example();
example.setProp1("test");
example.setProp2(13);

Map<String, String> map = PropertyMapFactory.getPropertyMap(example);

Assert.assertEquals("test", map.get("prop1"));
Assert.assertNull(map.get("prop2"));
}


}

The class created with ClassMock can have the common definition in the initialization method and only the different parts in the test methods. Using the framework static methods some behavior can be shared between the tests, like the part that create the instance and set the properties. The tests with ClassMock are also less verbose and easy to read.

Reflection Test Cases with ClassMock

With ClassMock, the classes to be used in the test may be defined programmatically and created at runtime. The net.sf.classmock.ClassMock class receives in the constructor the class name as a parameter. There is also an optional boolean parameter that define if the type created is an interface. Bellow there is a test class for the same component that gets a Java Bean and return a map with its properties:


package net.sf.classmock.example.property;

import static net.sf.classmock.ClassMockUtils.newInstance;
import static net.sf.classmock.ClassMockUtils.set;

import java.util.Map;

import net.sf.classmock.ClassMock;

import net.sf.classmock.Location;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class PropertyMapFactoryTest_ClassMock {

private ClassMock mockClass;

@Before
public void createMockClass(){
mockClass = new ClassMock("ExampleClassMock");
mockClass.addProperty("prop1", String.class)
.addProperty("prop2", int.class);
}

@Test
public void mapCreation(){
Object instance = createMockClassInstance();
Map<String, String> map = PropertyMapFactory.getPropertyMap(instance);

Assert.assertEquals("test", map.get("prop1"));
Assert.assertEquals("13", map.get("prop2"));
}

@Test
public void mapCreationWithIgnore(){
mockClass.addAnnotation("prop2", Ignore.class, Location.GETTER);
Object instance = createMockClassInstance();
Map<String, String> map = PropertyMapFactory.getPropertyMap(instance);

Assert.assertEquals("test", map.get("prop1"));
Assert.assertNull(map.get("prop2"));
}

private Object createMockClassInstance() {
Object instance = newInstance(mockClass);
set(instance,"prop1","test");
set(instance,"prop2",13);
return instance;
}

}

In traditional test cases for components that use reflection, there are classes defined in the test class or in the test method to be used. If the classes had some differences, the only way to reuse this definition code is inheritance. Bellow there is a test class for a component that gets a Java Bean and return a map with its properties:

ClassMock Methods

There are other methods that can be used to create a classe. Table 1 shows all the existent method names. Many methods are overloaded to easy the definition. All the definition methods return the ClassMock instance, using the fluent interface concept.

Table 1 - The net.sf.classmock.ClassMock methods for class creation.
Method Name  Description
setRealName Set class real name. If this parameter is not set, the class name will receive a counter in the generation to avoid the creation of classes with the same name.
addProperty Add a property (attribute, getter and setter). There are optional parameters if getter and setter must be created.
addPropertyReadOnly Add a read only property (attribute and getter)
addAnnotationAdd an annotation. If the first parameter is a String, the annotation will be added in the property with the same name. Otherwise the annotation will be created on the class.
addAnnotationPropertyAdd a property in an existent annotation. If the property is an annotation, the parameter must be an instance of net.sf.classmock.Annotation.
addMethodAdd an abstract method.
addMethodAnnotationAdd an annotation in a existent method.
addMethodAnnotationPropertyAdd a property in a existent annotation in an existent method.
addMethodParamAnnotationAdd an annotation in a parameter of an existent method.
addMethodParamAnnotationPropertyAdd a property in a existent annotation in a parameter of an existent method.
setSuperclassSet the superclass of the created class.
addInterfaceAdd an interface that the class will implement.
createClassCreate the defined class.

ClassMock had also some static methods that can be used to interact with the created classes. All the static methods are in the class net.sf.classmock.ClassMockUtils and are listed in the Table 2.

Table 2 - The net.sf.classmock.ClassMockUtils methods for interaction with the created classes.
Method Name  Description
set Set a property.
get Get a property.
newInstance Create the instance of a runtime generated class. Receive an instance of ClassMock as a parameter.
invokeInvoke a method. If the method parameters are not specified, the first method found with the same name will be invoked.

Below there is a class example that create a class and read its structure and annotations.

package net.sf.classmock.example.basic;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.objectweb.asm.Type;


import net.sf.classmock.ClassMock;
import net.sf.classmock.Location;


public class TesteGeneration {


public static void main(String[] args) {
System.out.println(Type.getType(Object.class).getInternalName());
ClassMock mock = new ClassMock("Mock");
mock.setSuperclass(Superclasse.class)
.addInterface(Interface.class)
.addInterface(Comparable.class)
.addAnnotation(Label.class,"Crasse")
.addProperty("nome", String.class)
.addAnnotation("nome", Teste.class,Location.GETTER,"teste")
.addAnnotationProperty("nome", Teste.class, "valor", 23)
.addAnnotationProperty("nome", Teste.class, "array", new String[]{"A","B","C"})
.addAnnotationProperty("nome", Teste.class, "enumeration", TesteEnum.TESTE2)
.addAnnotationProperty("nome", Teste.class, "label",new net.sf.classmock.Annotation(Label.class,"1"))
.addAnnotationProperty("nome", Teste.class, "labels",
new net.sf.classmock.Annotation[]{
new net.sf.classmock.Annotation(Label.class,"A"),
new net.sf.classmock.Annotation(Label.class,"B")
})
.addProperty("idade", long.class)
.addMethod(String.class, "testar", int.class, int.class)
.addMethodAnnotation("testar", Teste.class,"OK")
.addMethodAnnotationProperty("testar", Teste.class, "array", new String[]{"12","34"})
.addMethodParamAnnotation(0, "testar", Domain.class, "domain")
.addMethodParamAnnotationProperty(0, "testar", Domain.class, "outra", 23);

Class classe = mock.createClass();

System.out.println("Superclasse: "+classe.getSuperclass().getName());

for(Class interf : classe.getInterfaces()){
System.out.println("Interface: "+interf.getName());
}

for(Annotation a : classe.getAnnotations()){
System.out.println(" "+a.annotationType());
if(a.annotationType() == Label.class){
System.out.println(" value="+((Label)a).value());
}
}
System.out.println("--------");
for(Method m : classe.getMethods()){
System.out.println(m.getName());
for(Annotation a : m.getAnnotations()){
System.out.println(" "+a.annotationType());
if(a.annotationType() == Teste.class){
System.out.println(" valor="+((Teste)a).valor());
System.out.println(" value="+((Teste)a).value());
System.out.println(" enumeration="+((Teste)a).enumeration());
System.out.println(" array="+Arrays.toString(((Teste)a).array()));
System.out.println(" label="+((Teste)a).label().value());
System.out.println(" labels=");
for(Label l : ((Teste)a).labels()){
System.out.println(" @Label("+l.value()+")");
}
}
}
for (int i = 0; i < m.getParameterTypes().length; i++) {
for (int j = 0; j < m.getParameterAnnotations()[i].length; j++) {
System.out.println(" Param "+i+" annotation @"+m.getParameterAnnotations()[i][j].annotationType().getName());
if(m.getParameterAnnotations()[i][j].annotationType() == Domain.class){
System.out.println(" value="+((Domain)m.getParameterAnnotations()[i][j]).value());
System.out.println(" outra="+((Domain)m.getParameterAnnotations()[i][j]).outra());
}
}
}
}
System.out.println("--------");
for(Field f : classe.getDeclaredFields()){
System.out.println(f.getType() +" "+ f.getName());
for(Annotation a : f.getAnnotations()){
System.out.println(a.annotationType());
}
}

}

}

ClassMock and Mock Objects

Mock objects can be used to create instances with a desired behavior and expectations for a given class structure. Mock objects can also verify if the tested class behavior are the expected. ClassMock create new class structures to be used in a test. So, mock objects can be created with classes created with ClassMock. Below there is a test class that uses ClassMock and JMock 2 to test a dynamic proxy that authorize the access based on annotations. The ClassMock static methods must be used to define the expectations.


package net.sf.classmock.example.method;

import static net.sf.classmock.ClassMockUtils.invoke;
import net.sf.classmock.ClassMock;

import org.jmock.Expectations;
import org.jmock.Mockery;

import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith(JMock.class)
public class AuthorizationProxyTest {

Mockery context = new JUnit4Mockery(){{
setImposteriser(ClassImposteriser.INSTANCE);
}};

private Object mock;

@Before
public void createMock(){
ClassMock classMock = new ClassMock("DummyInterface",true);
classMock.addMethod(void.class,"execute")
.addMethodAnnotation("execute", AuthorizedRoles.class,new String[]{"admin"});
Class interf = classMock.createClass();

mock = context.mock(interf);
}

@Test
public void authorizedMethod() throws Throwable {
Object proxy = AuthorizationProxy.createProxy(mock, new User("john","admin"));
context.checking(new Expectations() {{
invoke(one(mock),"execute");
}});
invoke(proxy,"execute");
}

@Test(expected=AuthorizationException.class)
public void unauthorizedMethod() throws Throwable {
Object proxy = AuthorizationProxy.createProxy(mock, new User("john","operator"));

context.checking(new Expectations() {{
invoke(never(mock),"execute");
}});

invoke(proxy,"execute");
}

}

Contact

For ClassMock support, please use the project forum. To contact ClassMock creator, send an email to guerraem "at" gmail "dot" com.

Last update - 24/06/2008