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.
The ClassMock framework has a very intuitive API that allows you to:
The first step is to add the library file in your project.
Add in your pom.xml file
<dependency> <groupId>net.sf.esfinge</groupId> <artifactId>classmock</artifactId> <version>2.2</version> </dependency>
The jar library can be download at Maven Central.
This section is necessary to understand some keywords that you will find at javadoc.
WARNING: At byte code level all things should be informed as it would be by the compiler. Example all java classes extend Object even not explicit by the programmer. This is implicitly resolved by the compiler. But you must remember that you are creating entities at runtime without the compiler.
As you can see bellow this project tries to act like the compiler would do it for you.
// Optionally you can declare the package of your entity. // This allows you to create two or more entities with the same name, but in a different package Class<?> yourClass = ClassMock.of("my.pack.org.fake.domain.YoursUniqueDynamicClassName").build(); // Creates a Concrete Class // Automatic add the SUPER modifier // Automatic set Object.class as your superclass() final Class<?> clazz = ClassMock.of("MyEspecialClass").build(); // Implicitly defines a concrete class (Default) final Class<?> clazz = ClassMock.of("MyEspecialClass2") .asClass() // Explicit defines a concrete class .build(); // Creates an Abstract Class // Automatic add the SUPER and ABSTRACT modifiers // Automatic set Object.class as your superclass() final Class<?> absClazz = ClassMock.of("AbstractMyEspecialClass") .asAbstract() .build(); // Creates an Interface // Automatic add the INTERFACE and ABSTRACT modifiers // Automatic set Object.class as your superclass() final Class<?> interf = ClassMock.of("IMyEspecialClass") .asInterface() .build(); // Creates an Annotation // Automatic add the INTERFACE, ABSTRACT, and ANNOTATION modifiers // Automatic set Object.class as your superclass() // Automatic set Annotation.class as one of yours interfaces() final Class<?> annotationClazz = ClassMock.of("MyEspecialAnnotationClass") .asAnnotation() .build(); // Creates an Enum // Automatic add the SUPER, FINAL, and ENUM modifiers // Automatic set Enum.class as your superclass() final Class<?> enumClazz = ClassMock.of("MyEspecialAnnotationClass") .asEnum() .build();
The default properties for fields are: - Visibility on PRIVATE. - Always generate getter method. - Always generate setter method.
As this is the default you don't have to specify nothing.
final IClassWriter mock = ClassMock.of("SingleClassName"); // Concrete Class // Add field mock.field("id", Integer. class) // Name and class type of the field .annotation(Id.class) // JPA Annotation for Primary Key .and() // And add another annotation to this field .annotation(Column.class) // JPA Annotation for column .property("name", "ID_CODE"); // On the annotation above set the property "name" with the value "ID_CODE" // Add field mock.field("name", String.class) // Name and class type of the field .annotation(Column.class) // JPA Annotation for column .property("name", "TX_NAME"); // On the annotation above set the property "name" with the value "TX_NAME"
Note in the example bellow the presence of the STATIC modifier.
final IClassWriter mock = ClassMock.of("SingleClassName"); // Concrete Class mock.interfaces(Serializable.class); // Implements Serializable // Add field mock.field("serialVersionUID", long.class) // Name and class type of the field .hasGetter(false) // Disable the generation of the getter method .hasSetter(false) // Disable the generation of the getter method .modifiers(ModifierEnum.STATIC, ModifierEnum.FINAL) // MANDATORY - The STATIC modifier .value(4376923548604344061L) // Initialize the constant .visibility(VisibilityEnum.PRIVATE); // Visibility of the field
This is used when you want to create an Enum constants. It is possible to add to the enum all kinds of field from the section 5.2.1 and 5.2.2
// Define Enum type as your output entity. final IClassWriter mock = ClassMock.of(this.getClassName()).asEnum(); // Makes a collection from an array Arrays.asList("BMW", "BENTLEY", "PORSCHE", "CADILLAC", "LEXUS", "FERRARI", "MERCEDES", "FORD") .forEach(car -> { // Java 8 lambda // Create enum constants mock.field(car, Enum.class) // Name and class type of the field .hasGetter(false) // MANDATORY - Disable the generation of the getter method .hasSetter(false) // MANDATORY - Disable the generation of the setter method .visibility(VisibilityEnum.PUBLIC) // MANDATORY - Visibility on PUBLIC .modifiers(ModifierEnum.FINAL, // MANDATORY - Modifier FINAL ModifierEnum.STATIC, // MANDATORY - Modifier FINAL ModifierEnum.ENUM); // MANDATORY - Modifier ENUM }); // Creates an Enum type final Class<?> clazzEnum = mock.build(); // Get an array of your enum constants final Object[] enumConstants = clazzEnum.getEnumConstants();
It is possible to define a field from a String input. You can define: the class type, the name, the modifiers and even add annotations to the field. You can omit the ";"
WARNING: When you declare the class type always prefer to declare it with the package. It's possible to declare without the package, but we can't guarantee that the one found is the one you imagine to be loading. Because is possible to have the same name in different packages.
// Field with JoinColumn annotation final StringBuilder sb = new StringBuilder(); sb.append("@javax.persistence.JoinColumn(name = \"MY_COLUMN_NAME\", nullable = false)").append("\n"); sb.append("private String ").append(this.fieldName); final IClassWriter mock = ClassMock.of("SingleClassName"); // Concrete Class // Add field mock.fieldByParse(sb.toString()) // Parse the definition .hasGetter(true) // As is the default can be omitted .hasSetter(true) // As is the default can be omitted .annotation(Id.class) // JPA Annotation for Primary Key .and() // And add another annotation to this field .annotation(Column.class) // JPA Annotation for column .property("name", "MY_PRIMARY_KEY"); // On the annotation above set the property "name" with the value "MY_PRIMARY_KEY" // Creates a Concrete Class final Class<?> clazz = mock.build();
The default properties for methods are: - Visibility on PUBLIC. - Void as the default return type.
WARNING: ClassMock only creates a method signature, at this moment it is not possible to implement the method signature, but you can bypass this with techniques like proxies and mocks.
As this is the default you don't have to specify nothing.
final IClassWriter mock = ClassMock.of("SingleClassName"); // Concrete Class // Example without parameters mock.method("testIt") // The method name .returnTypeAsVoid() // As is the default can be omitted .exceptions(Exception.class) // Throws Exception .annotation(Override.class); // Annotated with Override annotation // Creates a Concrete Class final Class<?> clazz = mock.build();
Note in the example bellow the presence of the STATIC modifier.
final IClassWriter mock = ClassMock.of("SingleClassName"); // Concrete Class mock.method("testIt") // The method name .modifiers(ModifierEnum.STATIC) // MANDATORY - The STATIC modifier .returnTypeAsVoid() // As is the default can be omitted .exceptions(Exception.class) // Throws Exception .annotation(Override.class); // Annotated with Override annotation // Creates a Concrete Class final Class<?> clazz = mock.build();
This is used when you want to create an Annotation
// Define Annotation type as your output entity. final IClassWriter mock = ClassMock.of(this.getClassName()).asAnnotation(); // MANDATORY This line because makes possible to infer by reflection all the class definition mock.annotation(Retention.class) .property(RetentionPolicy.RUNTIME); // Add Method mock.method("alias") // The method name .returnType(String.class) // String as the return type .visibility(VisibilityEnum.PUBLIC) // MANDATORY - Visibility on PUBLIC .modifiers(ModifierEnum.ABSTRACT); // MANDATORY - Modifier ABSTRACT // Add Method mock.method("query") // The method name .returnType(String.class) // String as the return type .visibility(VisibilityEnum.PUBLIC) // MANDATORY - Visibility on PUBLIC .modifiers(ModifierEnum.ABSTRACT); // MANDATORY - Modifier ABSTRACT // Add Method mock.method("active") // The method name .returnType(Boolean.class) // Boolean as the return type .visibility(VisibilityEnum.PUBLIC) // MANDATORY - Visibility on PUBLICs .modifiers(ModifierEnum.ABSTRACT) // MANDATORY - Modifier ABSTRACT .value(Boolean.TRUE); // Set a default value for this method // Creates an Annotation final Class<?> annotation = mock.build();
It is possible to define a method from a String input. You can define: the class type, the name, the modifiers and even add annotations to the field. You can omit () and {}.
WARNING: When you declare the class type always prefer to declare it with the package. It's possible to declare without the package, but we can't guarantee that the one found is the one you imagine to be loading. Because is possible to have the same name in different packages.
// Method with JoinColumn annotation, note the JoinColumn package! final StringBuilder sb = new StringBuilder(); sb.append("@jabax.annotation.example.JoinColumn(name = \"MY_COLUMN_NAME\", nullable = false)").append(" "); sb.append("public void ").append(this.methodName).append("(){}"); final IClassWriter mock = ClassMock.of("SingleClassName"); // Concrete Class mock.methodByParse(sb.toString()) // Parse the definition // Creates a Concrete Class final Class<?> annotation = mock.build();
See our tests section in the project.
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.esfinge.classmock.example.property; import java.util.Map; import org.testng.Assert; import org.testng.annotations.Test; @SuppressWarnings("unused") public class PropertyMapFactoryTest { @Test public void mapCreation() { class Example { private String prop1; private int prop2; public String getProp1() { return this.prop1; } public void setProp1(final String prop1) { this.prop1 = prop1; } public int getProp2() { return this.prop2; } public void setProp2(final int prop2) { this.prop2 = prop2; } } final Example example = new Example(); example.setProp1("test"); example.setProp2(13); final 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 this.prop1; } public void setProp1(final String prop1) { this.prop1 = prop1; } @Ignore public int getProp2() { return this.prop2; } public void setProp2(final int prop2) { this.prop2 = prop2; } } final Example example = new Example(); example.setProp1("test"); example.setProp2(13); final 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.esfinge.classmock.ClassMock class receives in the constructor the class name as a parameter. 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.esfinge.classmock.example.property; import java.util.Map; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import net.sf.esfinge.classmock.ClassMock; import net.sf.esfinge.classmock.ClassMockUtils; import net.sf.esfinge.classmock.api.IClassWriter; import net.sf.esfinge.classmock.api.enums.LocationEnum; import net.sf.esfinge.classmock.example.basic.FactoryIt; public class PropertyMapFactoryTest_ClassMock { private IClassWriter mockClass; @BeforeTest public void createMockClass() { this.mockClass = ClassMock.of(FactoryIt.getName()); this.mockClass.field("prop1", String.class); this.mockClass.field("prop2", int.class); } @Test public void mapCreation() { final Object instance = this.createMockClassInstance(); final Map<String, String> map = PropertyMapFactory.getPropertyMap(instance); Assert.assertEquals("test", map.get("prop1")); Assert.assertEquals("13", map.get("prop2")); } @Test public void mapCreationWithIgnore() { this.mockClass.field("prop3", Integer.class).annotation(Ignore.class, LocationEnum.GETTER); final Object instance = this.createMockClassInstance(); final Map<String, String> map = PropertyMapFactory.getPropertyMap(instance); Assert.assertEquals("test", map.get("prop1")); Assert.assertNull(map.get("prop3")); } private Object createMockClassInstance() { this.mockClass.name(FactoryIt.getName()); final Object instance = ClassMockUtils.newInstance(this.mockClass); ClassMockUtils.set(instance, "prop1", "test"); ClassMockUtils.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 |
name | Set class name. |
field | Add a field (attribute, getter and setter). There are optional parameters if getter and setter must be created. |
method | Add an method. |
annotation | Add an annotation at class level. |
superclass | Set the superclass of the created class. |
interfaces | Add an interface that the class will implement. |
build | 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.esfinge.classmock.example.basic; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import net.sf.esfinge.classmock.ClassMock; import net.sf.esfinge.classmock.api.IAnnotationReader; import net.sf.esfinge.classmock.api.IClassWriter; import net.sf.esfinge.classmock.api.enums.LocationEnum; import net.sf.esfinge.classmock.imp.AnnotationImp; import net.sf.esfinge.classmock.imp.MethodImp; public class TesteGeneration { private Class<?> classe; @BeforeTest private void setUp() { final AnnotationImp label1 = new AnnotationImp(Label.class); label1.property("1"); final AnnotationImp label2 = new AnnotationImp(Label.class); label2.property("2"); final IAnnotationReader[] arrayLabels = { label1, label2 }; final MethodImp method = new MethodImp("testar"); method.annotation(Teste.class) .property("OK") .property("array", new String[] { "12", "34" }); method.returnType(String.class); method.parameter("op1", int.class) .annotation(Domain.class) .property("domain") .property("outra", 23); method.parameter("op2", int.class); final IClassWriter mock = ClassMock.of(FactoryIt.getName()); mock.superclass(Superclasse.class); mock.interfaces(Interface.class); mock.annotation(Label.class).property("Crasse"); mock.field("idade", long.class); mock.field("nome", String.class) .annotation(Teste.class, LocationEnum.FIELD) .property("teste") .property("valor", 23) .property("array", new String[] { "A", "B", "C" }) .property("enumeration", TesteEnum.TESTE2) .property("classe", ClassMock.class) .property("label", label1) .property("labels", arrayLabels); mock.method(method); this.classe = mock.build(); } @Test public void generationClass() { Assert.assertEquals(Superclasse.class, this.classe.getSuperclass()); Assert.assertEquals("Crasse", this.classe.getAnnotation(Label.class).value()); for (final Class<?> interf : this.classe.getInterfaces()) { Assert.assertEquals(Interface.class, interf); } } @Test public void generationField() { for (final Field f : this.classe.getDeclaredFields()) { if ("nome".equals(f.getName())) { final Teste teste = f.getAnnotation(Teste.class); Assert.assertEquals(teste.value(), "teste"); Assert.assertEquals(teste.valor(), 23); Assert.assertEquals(teste.array(), new String[] { "A", "B", "C" }); Assert.assertEquals(teste.enumeration(), TesteEnum.TESTE2); Assert.assertEquals(teste.classe(), ClassMock.class); Assert.assertEquals(teste.label().value(), "1"); final Label[] labels = teste.labels(); Assert.assertEquals(labels[0].value(), "1"); Assert.assertEquals(labels[1].value(), "2"); } } } @Test public void generationMethod() { for (final Method m : this.classe.getMethods()) { if ("testar".equals(m.getName())) { // Annotation final Teste methodAnnotation = m.getAnnotation(Teste.class); Assert.assertEquals(methodAnnotation.value(), "OK"); Assert.assertEquals(methodAnnotation.array(), new String[] { "12", "34" }); // Return type Assert.assertEquals(m.getReturnType(), String.class); // Parameters for (final Parameter parameter : m.getParameters()) { if ("arg0".equals(parameter.getName())) { final Domain domain = parameter.getAnnotation(Domain.class); Assert.assertEquals(domain.value(), "domain"); Assert.assertEquals(domain.outra(), 23); Assert.assertEquals(int.class, parameter.getType()); } else { Assert.assertEquals("arg1", parameter.getName()); Assert.assertEquals(int.class, parameter.getType()); } } } } } }
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.esfinge.classmock.example.method; import static net.sf.esfinge.classmock.ClassMockUtils.invoke; import net.sf.esfinge.classmock.ClassMock; import net.sf.esfinge.classmock.api.IClassWriter; import net.sf.esfinge.classmock.api.enums.ModifierEnum; 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() { final IClassWriter classMock = ClassMock.of("DummyInterface").asInterface(); classMock.method("execute") .returnTypeAsVoid() .modifiers(ModifierEnum.ABSTRACT) .annotation(AuthorizedRoles.class) .property(new String[] { "admin" }); final Class<?> interf = classMock.build(); this.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"); } }
For ClassMock support, please use the project forum. To contact ClassMock creator, send an email to guerraem@gmail.com
Support