1 package ru.indvdum.jpa.tests
4 import static org.junit.Assert.*
6 import java.lang.reflect.Field
7 import java.lang.reflect.Method
8 import java.lang.reflect.Modifier
10 import javax.persistence.EmbeddedId
11 import javax.persistence.Entity
12 import javax.persistence.GeneratedValue
13 import javax.persistence.Id
14 import javax.persistence.IdClass
15 import javax.persistence.Transient
19 import ru.indvdum.jpa.dao.JPADataAccessObject
20 import ru.indvdum.jpa.entities.AbstractEntity
24 * JUnit test case for testing of a creating, listing, updating and removing
25 * operations with database of the JPA entities.
27 * @author indvdum (gotoindvdum@gmail.com)
28 * @since 23.12.2011 22:54:26
31 abstract class AbstractJPAEntityTest extends AbstractJPATest {
33 protected def uniqueValue = 1
34 protected JPADataAccessObject dao = null
35 protected Set toRemove = new HashSet()
36 protected Map<Class, Integer> existedEntitiesCount = [:]
39 public void testEntity() {
42 testEntity(getEntityClass())
43 } catch (Throwable t) {
52 * @return Your implementation of JPADataAccessObject
54 protected abstract JPADataAccessObject createDAO();
57 * Test creating, listing, updating, and removing of an {@code entityClass} object
61 protected void testEntity(Class entityClass) {
63 assert AbstractEntity.class.isAssignableFrom(entityClass)
65 Map<String, Object> fieldsValues
69 def entity = createEntity(entityClass)
70 testCreatedEntity(entity)
74 assert dao.list(it.class).each { it2 ->
75 assert it.class.isAssignableFrom(it2.class)
76 }.size() == toRemove.findAll { it2 ->
77 it.class.isAssignableFrom(it2.class)
78 }.size() + existedEntitiesCount[it.class]
82 fieldsValues = updateFields(entity)
83 assert dao.persist(entity)
84 assert dao.contains(entity)
85 dbEntity = dao.find(entityClass, (entity as AbstractEntity).getIdentifierValue())
86 assert dbEntity != null
87 assert checkEntityFieldValues(dbEntity, fieldsValues)
88 testUpdatedEntity(entity)
91 assert dao.remove(toRemove)
92 assert !dao.contains(toRemove)
93 testRemovedEntity(entity)
97 * Create and persist to database an {@code entityClass} object
100 * @return created entity object
102 protected Object createEntity(Class entityClass) {
103 if (entityClass.isInterface())
105 assertNotNull entityClass.annotations.find {it instanceof Entity}
106 def entity = entityClass.newInstance()
107 assert entity.class == entityClass
109 if (!existedEntitiesCount.containsKey(entityClass))
110 existedEntitiesCount[entityClass] = dao.list(entityClass).size()
112 Map<String, Object> fieldsValues = updateFields(entity)
113 assert dao.persist(entity)
114 assert dao.contains(entity)
115 def dbEntity = dao.find(entityClass, (entity as AbstractEntity).getIdentifierValue())
116 assert dbEntity != null
117 assert checkEntityFieldValues(dbEntity, fieldsValues)
123 * Update all fields of an {@code entity} object
125 * @return generated field values
127 protected Map<String, Object> updateFields(Object entity) {
128 Map<String, Object> fieldsValues = new HashMap<String, Object>()
129 getFields(entity).grep {
130 it.annotations.find {
131 it instanceof Transient || it instanceof GeneratedValue
134 def newValue = generateFieldValue(entity, it)
135 setFieldValueBySetter(entity, it, newValue)
136 fieldsValues.put(it.name, newValue)
142 * Collections and arrays will not be processed
146 * @return generated field value
148 protected Object generateFieldValue(Object entity, Field field) {
149 def type = field.getType()
151 if(type.toString() == 'boolean' || type == Boolean.class) {
152 newValue = (boolean) (uniqueValue++ % 2i == 0i)
153 } else if(type.toString() == 'byte' || type == Byte) {
154 newValue = (byte) (uniqueValue++ % Byte.MAX_VALUE + 1i)
155 } else if(type.toString() == 'char' || type == Character) {
156 newValue = (char) (uniqueValue++ % (int) Character.MAX_VALUE + 1i)
157 } else if(type.toString() == 'short' || type == Short) {
158 newValue = (short) (uniqueValue++ % Short.MAX_VALUE + 1i)
159 } else if(type.toString() == 'int' || type == Integer) {
160 newValue = (int) (uniqueValue++ % Integer.MAX_VALUE + 1i)
161 } else if(type.toString() == 'long' || type == Long) {
162 newValue = (long) uniqueValue++ % Long.MAX_VALUE + 1L
163 } else if(type.toString() == 'float' || type == Float) {
164 newValue = (float) uniqueValue++ % Float.MAX_VALUE + 1f
165 } else if(type.toString() == 'double' || type == Double) {
166 newValue = (double) uniqueValue++ % Double.MAX_VALUE + 1d
167 } else if(Number.class.isAssignableFrom(type)) {
168 // trying to use constructor with int argument
169 newValue = type.newInstance(uniqueValue++ % Byte.MAX_VALUE + 1i)
170 } else if(type == String.class) {
171 newValue = (String) "test${uniqueValue++}"
172 } else if(type instanceof Class && ((type as Class).isInterface() || (type as Class).annotations.find {it instanceof Entity} != null)) { // modifying of a primary keys is deprecated
173 // an attempt to use already created entities
174 def currentValue = getFieldValue(entity, field)
175 newValue = toRemove.find {it.class.isAssignableFrom(type) && it != currentValue}
177 newValue = createEntity(type as Class)
178 } else if(Enum.class.isAssignableFrom(type)) {
179 def values = type.values();
180 newValue = values[uniqueValue++ % values.size()];
182 type instanceof Class
184 field.clazz.annotations.find {it instanceof IdClass} != null
185 && field.declaredAnnotations.find {it instanceof Id} != null
187 && field.declaredAnnotations.find {it instanceof EmbeddedId} == null
188 && !Collection.class.isAssignableFrom(type)
189 && !(type as Class).isArray()
190 ) { // modifying of a primary keys is deprecated
191 newValue = (type as Class).newInstance()
193 newValue = getFieldValue(entity, field)
201 * @return {@code true}, if all entity fields is equals to {@code rightValues}
203 protected boolean checkEntityFieldValues(Object entity, Map<String, Object> rightValues) {
205 def fields = getFields(entity)
206 rightValues.each { key, value ->
207 fields.find { it.name == key }.each {
208 if(!getFieldValue(entity, it).equals(value)) {
217 * Set a {@code field} to a {@code value} of an entity {@code object}
223 protected void setFieldValue(Object object, Field field, Object value) {
224 boolean isAccessible = field.accessible
225 field.accessible = true
226 field.set(object, value)
227 field.accessible = isAccessible
231 * Trying set a {@code field} to a {@code value} of an entity {@code object}
232 * by using a setter method. If setter not found, field will be set directly
233 * throw {@code setFieldValue} method.
239 protected void setFieldValueBySetter(Object object, Field field, Object value) {
240 boolean isSetted = false
241 getMethods(object).grep {
243 Modifier.isPublic(it.modifiers)
244 && it.parameterTypes.size() == 1
247 || it.parameterTypes[0].isAssignableFrom(value.class)
249 && it.name =~ /^set(?i:${field.name.charAt(0)})${field.name.replaceFirst("^.{1}", "")}/
252 object."${it.name}"(value)
257 setFieldValue(object, field, value)
263 * @return the field value of an object
265 protected Object getFieldValue(Object object, Field field) {
266 boolean isAccessible = field.accessible
267 field.accessible = true
268 def value = field.get(object)
269 field.accessible = isAccessible
275 * @return all object fields, including inherited
277 protected Collection<Field> getFields(obj) {
278 Class clazz = obj.getClass()
279 Collection<Field> fields = clazz.declaredFields
280 while(clazz.superclass != null) {
281 clazz = clazz.superclass
282 fields.addAll(clazz.declaredFields)
286 !Modifier.isStatic(it.modifiers) &&
287 !Modifier.isTransient(it.modifiers)
293 * @return all object methods, including inherited
295 protected Collection<Method> getMethods(obj) {
296 Class clazz = obj.getClass()
297 Collection<Method> methods = clazz.declaredMethods
298 while(clazz.superclass != null) {
299 clazz = clazz.superclass
300 methods.addAll(clazz.declaredMethods)
304 !Modifier.isStatic(it.modifiers) &&
305 !Modifier.isTransient(it.modifiers)
310 * For implement in successors
314 protected void testCreatedEntity(Object entity) {
319 * For implement in successors
323 protected void testUpdatedEntity(Object entity) {
328 * For implement in successors
332 protected void testRemovedEntity(Object entity) {
337 * @return an {@link AbstractEntity} successor
339 abstract protected Class getEntityClass()