package org.springframework.roo.addon.test; import static org.springframework.roo.model.RooJavaType.ROO_INTEGRATION_TEST; import static org.springframework.roo.model.SpringJavaType.MOCK_STATIC_ENTITY_METHODS; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.springframework.roo.addon.dod.DataOnDemandOperations; import org.springframework.roo.classpath.PhysicalTypeCategory; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.TypeLocationService; import org.springframework.roo.classpath.TypeManagementService; import org.springframework.roo.classpath.customdata.CustomDataKeys; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.MemberHoldingTypeDetails; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue; import org.springframework.roo.classpath.details.annotations.ClassAttributeValue; import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder; import org.springframework.roo.classpath.scanner.MemberDetails; import org.springframework.roo.classpath.scanner.MemberDetailsScanner; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.FeatureNames; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.Path; import org.springframework.roo.project.ProjectOperations; /** * Provides convenience methods that can be used to create mock tests. * * @author Ben Alex * @since 1.0 */ @Component @Service public class IntegrationTestOperationsImpl implements IntegrationTestOperations { private static final JavaType JUNIT_4 = new JavaType( "org.junit.runners.JUnit4"); private static final JavaType RUN_WITH = new JavaType( "org.junit.runner.RunWith"); private static final JavaType TEST = new JavaType("org.junit.Test"); @Reference private DataOnDemandOperations dataOnDemandOperations; @Reference private MemberDetailsScanner memberDetailsScanner; @Reference private MetadataService metadataService; @Reference private ProjectOperations projectOperations; @Reference private TypeLocationService typeLocationService; @Reference private TypeManagementService typeManagementService; /** * @param entity the entity to lookup required * @return the type details (never null; throws an exception if it cannot be * obtained or parsed) */ private ClassOrInterfaceTypeDetails getEntity(final JavaType entity) { final ClassOrInterfaceTypeDetails cid = typeLocationService .getTypeDetails(entity); Validate.notNull(cid, "Java source code details unavailable for type " + cid); return cid; } public boolean isIntegrationTestInstallationPossible() { return projectOperations.isFocusedProjectAvailable() && projectOperations.isFeatureInstalledInFocusedModule( FeatureNames.JPA, FeatureNames.MONGO); } public void newIntegrationTest(final JavaType entity) { newIntegrationTest(entity, true); } public void newIntegrationTest(final JavaType entity, final boolean transactional) { Validate.notNull(entity, "Entity to produce an integration test for is required"); // Verify the requested entity actually exists as a class and is not // abstract final ClassOrInterfaceTypeDetails cid = getEntity(entity); Validate.isTrue(!Modifier.isAbstract(cid.getModifier()), "Type " + entity.getFullyQualifiedTypeName() + " is abstract"); final LogicalPath path = PhysicalTypeIdentifier.getPath(cid .getDeclaredByMetadataId()); dataOnDemandOperations.newDod(entity, new JavaType(entity.getFullyQualifiedTypeName() + "DataOnDemand")); final JavaType name = new JavaType(entity + "IntegrationTest"); final String declaredByMetadataId = PhysicalTypeIdentifier .createIdentifier(name, Path.SRC_TEST_JAVA.getModulePathId(path.getModule())); if (metadataService.get(declaredByMetadataId) != null) { // The file already exists return; } final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); final List<AnnotationAttributeValue<?>> config = new ArrayList<AnnotationAttributeValue<?>>(); config.add(new ClassAttributeValue(new JavaSymbolName("entity"), entity)); if (!transactional) { config.add(new BooleanAttributeValue(new JavaSymbolName( "transactional"), false)); } annotations.add(new AnnotationMetadataBuilder(ROO_INTEGRATION_TEST, config)); final List<MethodMetadataBuilder> methods = new ArrayList<MethodMetadataBuilder>(); final List<AnnotationMetadataBuilder> methodAnnotations = new ArrayList<AnnotationMetadataBuilder>(); methodAnnotations.add(new AnnotationMetadataBuilder(TEST)); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( declaredByMetadataId, Modifier.PUBLIC, new JavaSymbolName( "testMarkerMethod"), JavaType.VOID_PRIMITIVE, new InvocableMemberBodyBuilder()); methodBuilder.setAnnotations(methodAnnotations); methods.add(methodBuilder); final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder( declaredByMetadataId, Modifier.PUBLIC, name, PhysicalTypeCategory.CLASS); cidBuilder.setAnnotations(annotations); cidBuilder.setDeclaredMethods(methods); typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build()); } /** * Creates a mock test for the entity. Silently returns if the mock test * file already exists. * * @param entity to produce a mock test for (required) */ public void newMockTest(final JavaType entity) { Validate.notNull(entity, "Entity to produce a mock test for is required"); final JavaType name = new JavaType(entity + "Test"); final String declaredByMetadataId = PhysicalTypeIdentifier .createIdentifier(name, Path.SRC_TEST_JAVA .getModulePathId(projectOperations .getFocusedModuleName())); if (metadataService.get(declaredByMetadataId) != null) { // The file already exists return; } // Determine if the mocking infrastructure needs installing final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); final List<AnnotationAttributeValue<?>> config = new ArrayList<AnnotationAttributeValue<?>>(); config.add(new ClassAttributeValue(new JavaSymbolName("value"), JUNIT_4)); annotations.add(new AnnotationMetadataBuilder(RUN_WITH, config)); annotations.add(new AnnotationMetadataBuilder( MOCK_STATIC_ENTITY_METHODS)); final List<MethodMetadataBuilder> methods = new ArrayList<MethodMetadataBuilder>(); final List<AnnotationMetadataBuilder> methodAnnotations = new ArrayList<AnnotationMetadataBuilder>(); methodAnnotations.add(new AnnotationMetadataBuilder(TEST)); // Get the entity so we can hopefully make a demo method that will be // usable final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); final ClassOrInterfaceTypeDetails cid = typeLocationService .getTypeDetails(entity); if (cid != null) { final MemberDetails memberDetails = memberDetailsScanner .getMemberDetails( IntegrationTestOperationsImpl.class.getName(), cid); final List<MethodMetadata> countMethods = memberDetails .getMethodsWithTag(CustomDataKeys.COUNT_ALL_METHOD); if (countMethods.size() == 1) { final String countMethod = entity.getSimpleTypeName() + "." + countMethods.get(0).getMethodName().getSymbolName() + "()"; bodyBuilder.appendFormalLine("int expectedCount = 13;"); bodyBuilder.appendFormalLine(countMethod + ";"); bodyBuilder .appendFormalLine("org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl.expectReturn(expectedCount);"); bodyBuilder .appendFormalLine("org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl.playback();"); bodyBuilder .appendFormalLine("org.junit.Assert.assertEquals(expectedCount, " + countMethod + ");"); } } final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( declaredByMetadataId, Modifier.PUBLIC, new JavaSymbolName( "testMethod"), JavaType.VOID_PRIMITIVE, bodyBuilder); methodBuilder.setAnnotations(methodAnnotations); methods.add(methodBuilder); final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder( declaredByMetadataId, Modifier.PUBLIC, name, PhysicalTypeCategory.CLASS); cidBuilder.setAnnotations(annotations); cidBuilder.setDeclaredMethods(methods); typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build()); } public void newTestStub(final JavaType javaType) { Validate.notNull(javaType, "Class to produce a test stub for is required"); final JavaType name = new JavaType(javaType + "Test"); final String declaredByMetadataId = PhysicalTypeIdentifier .createIdentifier(name, Path.SRC_TEST_JAVA .getModulePathId(projectOperations .getFocusedModuleName())); if (metadataService.get(declaredByMetadataId) != null) { // The file already exists return; } // Determine if the test infrastructure needs installing final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>(); final List<AnnotationAttributeValue<?>> config = new ArrayList<AnnotationAttributeValue<?>>(); config.add(new ClassAttributeValue(new JavaSymbolName("value"), JUNIT_4)); annotations.add(new AnnotationMetadataBuilder(RUN_WITH, config)); final List<MethodMetadataBuilder> methods = new ArrayList<MethodMetadataBuilder>(); final List<AnnotationMetadataBuilder> methodAnnotations = new ArrayList<AnnotationMetadataBuilder>(); methodAnnotations.add(new AnnotationMetadataBuilder(TEST)); // Get the class so we can hopefully make a demo method that will be // usable final ClassOrInterfaceTypeDetails governorTypeDetails = typeLocationService .getTypeDetails(javaType); final MemberDetails memberDetails = memberDetailsScanner .getMemberDetails(this.getClass().getName(), governorTypeDetails); for (final MemberHoldingTypeDetails typeDetails : memberDetails .getDetails()) { if (!(typeDetails.getCustomData().keySet() .contains(CustomDataKeys.PERSISTENT_TYPE) || typeDetails .getDeclaredByMetadataId() .startsWith( "MID:org.springframework.roo.addon.tostring.ToStringMetadata"))) { for (final MethodMetadata method : typeDetails .getDeclaredMethods()) { // Check if public, non-abstract method if (Modifier.isPublic(method.getModifier()) && !Modifier.isAbstract(method.getModifier())) { final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); bodyBuilder .appendFormalLine("org.junit.Assert.assertTrue(true);"); final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder( declaredByMetadataId, Modifier.PUBLIC, method.getMethodName(), JavaType.VOID_PRIMITIVE, bodyBuilder); methodBuilder.setAnnotations(methodAnnotations); methods.add(methodBuilder); } } } } // Only create test class if there are test methods present if (!methods.isEmpty()) { final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder( declaredByMetadataId, Modifier.PUBLIC, name, PhysicalTypeCategory.CLASS); // Create instance of entity to test final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder( declaredByMetadataId); fieldBuilder.setModifier(Modifier.PRIVATE); fieldBuilder.setFieldName(new JavaSymbolName(StringUtils .uncapitalize(javaType.getSimpleTypeName()))); fieldBuilder.setFieldType(javaType); fieldBuilder.setFieldInitializer("new " + javaType.getFullyQualifiedTypeName() + "()"); final List<FieldMetadataBuilder> fields = new ArrayList<FieldMetadataBuilder>(); fields.add(fieldBuilder); cidBuilder.setDeclaredFields(fields); cidBuilder.setDeclaredMethods(methods); typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build()); } } }