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.
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.
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;
}
}
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.
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) |
addAnnotation | Add 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. |
addAnnotationProperty | Add a property in an existent annotation. If the property is an annotation, the parameter must be an instance of net.sf.classmock.Annotation. |
addMethod | Add an abstract method. |
addMethodAnnotation | Add an annotation in a existent method. |
addMethodAnnotationProperty | Add a property in a existent annotation in an existent method. |
addMethodParamAnnotation | Add an annotation in a parameter of an existent method. |
addMethodParamAnnotationProperty | Add a property in a existent annotation in a parameter of an existent method. |
setSuperclass | Set the superclass of the created class. |
addInterface | Add an interface that the class will implement. |
createClass | Create the defined class. |
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. |
invoke | Invoke a method. If the method parameters are not specified, the first method found with the same name will be invoked. |
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());
}
}
}
}
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");
}
}