Esfinge ClassMock



ClassMock - Test Tool for Metadata-Based and Reflection-Based Components

Table of contents

1. Introduction

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.

2. Features

The ClassMock framework has a very intuitive API that allows you to:

  1. create dynamic Annotation;
  2. create dynamic Enum;
  3. create dynamic Interface;
  4. create dynamic Abstract Class;
  5. create dynamic Concrete Class;
  6. Add a superclass;
  7. Add interfaces;
  8. Add instance fields using fluent API or parsing String;
  9. Add static fields using fluent API or parsing String;
  10. Add instance methods using fluent API or parsing String;
  11. Add static methods using fluent API or parsing String;
  12. Set java version for JRE compilation;
  13. Set visibility (PRIVATE, PUBLIC, PROTECTED);
  14. Set modifiers (VOLATILE, STATIC, FINAL, SYNCHRONIZED...);
  15. Apply annotations;
  16. Apply generics at superclass;

3. Install

The first step is to add the library file in your project.

3.1 Maven

Add in your pom.xml file

						<dependency>
						  <groupId>net.sf.esfinge</groupId>
						  <artifactId>classmock</artifactId>
						  <version>2.2</version>
						</dependency>
					

3.2 Download Jar

The jar library can be download at Maven Central.

4. Concepts

This section is necessary to understand some keywords that you will find at javadoc.

  • Entity: Is the final product that you aims to create, it can be: Concrete Class, Abstract Class, Enum, Interface or an Annotation
  • Modifiers: Are properties responsible for modify the definition of your Entity, methods or field. All this properties are well-known such as final, transient, static, abstract...
  • Visibility: Is a kind of Modifier, that is responsible for the level of access/visibility that you define as public, private and protected. It was separated from Modifiers to facilitate the usage.

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.

5. How to

5.1 Create an Entity

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();
					

5.2 Add fields to the Entity

The default properties for fields are: - Visibility on PRIVATE. - Always generate getter method. - Always generate setter method.

5.2.1 Instance Field

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"
					

5.2.2 Class Field

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
					

5.2.3 Enum 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();
					

5.2.4 Field from String

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();

5.3 Add methods to the Entity

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.

5.3.1 Instance Method

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();
					

5.3.2 Class Method

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();
					

5.3.3 Annotation Method

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();
					

5.3.4 Method from String

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();
					

5.4 More examples

See our tests section in the project.

6. 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.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.

7. 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.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;
							}
						}
					

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:

8. 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.esfinge.classmock.ClassMock methods for class creation.
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.

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

Table 2 - The net.sf.esfinge.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.
invoke Invoke 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.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());
											}
										}
									}
								}
							}
						}
					

9. 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.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");
							}
						}
					

License

MIT Licensed

Links

Download ClassMock

GitHub Project Page

ClassMock Forum

Contact

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






Support

All rights reserved