The Java Reflection API provides the ability to inspect and manipulate classes, methods, fields, and other components at runtime. In test automation, reflection is particularly powerful for creating dynamic test frameworks, data-driven testing, and building flexible test utilities that can adapt to different scenarios without code changes.
By the end of this module, you will be able to:
Reflection is the ability of a program to examine and modify its own structure and behavior at runtime. It allows you to:
@Test
public void testBasicReflection() throws Exception {
// Get class information
Class<String> stringClass = String.class;
// Get class name
assertEquals("java.lang.String", stringClass.getName());
// Get methods
Method[] methods = stringClass.getMethods();
assertTrue(methods.length > 0);
// Find specific method
Method lengthMethod = stringClass.getMethod("length");
assertNotNull(lengthMethod);
// Invoke method
String testString = "Hello World";
int length = (Integer) lengthMethod.invoke(testString);
assertEquals(11, length);
}
@Test
public void testObtainingClasses() throws Exception {
// Method 1: Using .class literal
Class<String> stringClass1 = String.class;
// Method 2: Using Class.forName()
Class<?> stringClass2 = Class.forName("java.lang.String");
// Method 3: Using getClass() on instance
String str = "test";
Class<?> stringClass3 = str.getClass();
// All should be the same
assertEquals(stringClass1, stringClass2);
assertEquals(stringClass2, stringClass3);
}
public class TestClassInspector {
@Test
public void testClassInspection() {
Class<ArrayList> listClass = ArrayList.class;
// Get package information
Package pkg = listClass.getPackage();
assertEquals("java.util", pkg.getName());
// Get superclass
Class<?> superClass = listClass.getSuperclass();
assertEquals("java.util.AbstractList", superClass.getName());
// Get interfaces
Class<?>[] interfaces = listClass.getInterfaces();
assertTrue(interfaces.length > 0);
// Check if it implements specific interface
assertTrue(List.class.isAssignableFrom(listClass));
}
@Test
public void testModifiers() {
Class<String> stringClass = String.class;
int modifiers = stringClass.getModifiers();
assertTrue(Modifier.isPublic(modifiers));
assertTrue(Modifier.isFinal(modifiers));
assertFalse(Modifier.isAbstract(modifiers));
}
}
public class TestUser {
private String name;
public int age;
private static String defaultRole = "user";
public TestUser(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters...
}
@Test
public void testFieldAccess() throws Exception {
TestUser user = new TestUser("John", 25);
Class<TestUser> userClass = TestUser.class;
// Access public field
Field ageField = userClass.getField("age");
assertEquals(25, ageField.get(user));
// Modify public field
ageField.set(user, 30);
assertEquals(30, ageField.get(user));
// Access private field
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true); // Make accessible
assertEquals("John", nameField.get(user));
// Modify private field
nameField.set(user, "Jane");
assertEquals("Jane", nameField.get(user));
}
@Test
public void testStaticFieldAccess() throws Exception {
Class<TestUser> userClass = TestUser.class;
// Access static field
Field roleField = userClass.getDeclaredField("defaultRole");
roleField.setAccessible(true);
assertEquals("user", roleField.get(null)); // null for static fields
// Modify static field
roleField.set(null, "admin");
assertEquals("admin", roleField.get(null));
}
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double multiply(double a, double b) {
return a * b;
}
private String formatResult(double result) {
return String.format("%.2f", result);
}
}
@Test
public void testMethodInvocation() throws Exception {
Calculator calc = new Calculator();
Class<Calculator> calcClass = Calculator.class;
// Invoke public method
Method addMethod = calcClass.getMethod("add", int.class, int.class);
int result = (Integer) addMethod.invoke(calc, 5, 3);
assertEquals(8, result);
// Invoke method with different parameter types
Method multiplyMethod = calcClass.getMethod("multiply", double.class, double.class);
double multiplyResult = (Double) multiplyMethod.invoke(calc, 2.5, 4.0);
assertEquals(10.0, multiplyResult, 0.001);
// Invoke private method
Method formatMethod = calcClass.getDeclaredMethod("formatResult", double.class);
formatMethod.setAccessible(true);
String formatted = (String) formatMethod.invoke(calc, 10.567);
assertEquals("10.57", formatted);
}
@Test
public void testConstructorInvocation() throws Exception {
Class<TestUser> userClass = TestUser.class;
// Get constructor
Constructor<TestUser> constructor = userClass.getConstructor(String.class, int.class);
// Create instance
TestUser user = constructor.newInstance("Alice", 28);
assertNotNull(user);
// Verify the object was created correctly
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
assertEquals("Alice", nameField.get(user));
Field ageField = userClass.getField("age");
assertEquals(28, ageField.get(user));
}
@Test
public void testDefaultConstructor() throws Exception {
Class<ArrayList> listClass = ArrayList.class;
// Create instance using default constructor
Constructor<ArrayList> constructor = listClass.getConstructor();
ArrayList<String> list = constructor.newInstance();
assertNotNull(list);
assertTrue(list.isEmpty());
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface TestInfo {
String description() default "";
String author() default "";
int priority() default 1;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataProvider {
String value() default "";
}
public class AnnotatedTestClass {
@TestInfo(description = "Tests user login functionality", author = "John", priority = 1)
@Test
public void testLogin() {
// Test implementation
}
@TestInfo(description = "Tests user registration", author = "Jane", priority = 2)
@Test
public void testRegistration() {
// Test implementation
}
@DataProvider("loginData")
public Object[][] getLoginData() {
return new Object[][]{
{"user1", "pass1"},
{"user2", "pass2"}
};
}
}
@Test
public void testAnnotationProcessing() throws Exception {
Class<AnnotatedTestClass> testClass = AnnotatedTestClass.class;
// Get all methods
Method[] methods = testClass.getDeclaredMethods();
for (Method method : methods) {
// Check if method has TestInfo annotation
if (method.isAnnotationPresent(TestInfo.class)) {
TestInfo testInfo = method.getAnnotation(TestInfo.class);
System.out.println("Method: " + method.getName());
System.out.println("Description: " + testInfo.description());
System.out.println("Author: " + testInfo.author());
System.out.println("Priority: " + testInfo.priority());
}
// Check for DataProvider annotation
if (method.isAnnotationPresent(DataProvider.class)) {
DataProvider dataProvider = method.getAnnotation(DataProvider.class);
System.out.println("Data Provider: " + dataProvider.value());
}
}
}
public class ReflectionTestRunner {
public void runTests(Class<?> testClass) throws Exception {
Object testInstance = testClass.newInstance();
Method[] methods = testClass.getDeclaredMethods();
// Run setup methods first
runMethodsWithAnnotation(testInstance, methods, "BeforeEach");
// Run test methods
for (Method method : methods) {
if (method.isAnnotationPresent(Test.class)) {
runSingleTest(testInstance, method);
}
}
// Run cleanup methods
runMethodsWithAnnotation(testInstance, methods, "AfterEach");
}
private void runSingleTest(Object testInstance, Method testMethod) {
try {
System.out.println("Running test: " + testMethod.getName());
// Check for TestInfo annotation
if (testMethod.isAnnotationPresent(TestInfo.class)) {
TestInfo info = testMethod.getAnnotation(TestInfo.class);
System.out.println("Priority: " + info.priority());
System.out.println("Description: " + info.description());
}
testMethod.invoke(testInstance);
System.out.println("✓ Test passed: " + testMethod.getName());
} catch (Exception e) {
System.err.println("✗ Test failed: " + testMethod.getName());
System.err.println("Error: " + e.getCause().getMessage());
}
}
private void runMethodsWithAnnotation(Object testInstance, Method[] methods,
String annotationName) throws Exception {
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().getSimpleName().equals(annotationName)) {
method.invoke(testInstance);
}
}
}
}
}
public class DataDrivenTestRunner {
public void runDataDrivenTest(Class<?> testClass, String testMethodName)
throws Exception {
Object testInstance = testClass.newInstance();
Method testMethod = testClass.getMethod(testMethodName, String.class, String.class);
// Find data provider method
Method dataProviderMethod = findDataProviderMethod(testClass, testMethodName);
if (dataProviderMethod != null) {
Object[][] testData = (Object[][]) dataProviderMethod.invoke(testInstance);
for (int i = 0; i < testData.length; i++) {
Object[] row = testData[i];
System.out.println("Running test with data set " + (i + 1));
try {
testMethod.invoke(testInstance, row);
System.out.println("✓ Test passed with data: " + Arrays.toString(row));
} catch (Exception e) {
System.err.println("✗ Test failed with data: " + Arrays.toString(row));
System.err.println("Error: " + e.getCause().getMessage());
}
}
}
}
private Method findDataProviderMethod(Class<?> testClass, String testMethodName) {
Method[] methods = testClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(DataProvider.class)) {
DataProvider annotation = method.getAnnotation(DataProvider.class);
if (annotation.value().equals(testMethodName + "Data")) {
return method;
}
}
}
return null;
}
}
public class ReflectionCache {
private static final Map<String, Method> methodCache = new ConcurrentHashMap<>();
private static final Map<String, Field> fieldCache = new ConcurrentHashMap<>();
public static Method getCachedMethod(Class<?> clazz, String methodName,
Class<?>... parameterTypes) throws NoSuchMethodException {
String key = clazz.getName() + "." + methodName + "(" +
Arrays.toString(parameterTypes) + ")";
return methodCache.computeIfAbsent(key, k -> {
try {
return clazz.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
public static Field getCachedField(Class<?> clazz, String fieldName)
throws NoSuchFieldException {
String key = clazz.getName() + "." + fieldName;
return fieldCache.computeIfAbsent(key, k -> {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
});
}
}
public class SafeReflectionUtils {
public static Optional<Object> invokeMethodSafely(Object instance,
String methodName,
Object... args) {
try {
Class<?> clazz = instance.getClass();
Class<?>[] paramTypes = Arrays.stream(args)
.map(Object::getClass)
.toArray(Class[]::new);
Method method = clazz.getMethod(methodName, paramTypes);
Object result = method.invoke(instance, args);
return Optional.ofNullable(result);
} catch (Exception e) {
System.err.println("Failed to invoke method: " + methodName);
System.err.println("Error: " + e.getMessage());
return Optional.empty();
}
}
public static Optional<Object> getFieldValueSafely(Object instance,
String fieldName) {
try {
Field field = instance.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
Object value = field.get(instance);
return Optional.ofNullable(value);
} catch (Exception e) {
System.err.println("Failed to get field value: " + fieldName);
System.err.println("Error: " + e.getMessage());
return Optional.empty();
}
}
}