using EasyRandom with JUnit5
Tags: testing, junit5, easyrandom
Published: 2022-03-08 07:17:48

When you use EasyRandom, You'd have written following code for each test:

```java
EasyRandom easyRandom = new EasyRandom();
... = easyRandom.nextObject(...);
```

What if we remove that? Make this:

```java
@Test
public void testSortAlgorithm() {

   // Given
   int[] ints = easyRandom.nextObject(int[].class);

   // When
   int[] sortedInts = myAwesomeSortAlgo.sort(ints);

   // Then
   assertThat(sortedInts).isSorted();

}
```

into this:

```java
@Test
@ExtendWith(EasyRandomExtension.class)
public void testSortAlgorithm(@EasyRandomed int[] ints) {
   // When
   int[] sortedInts = myAwesomeSortAlgo.sort(ints);

   // Then
   assertThat(sortedInts).isSorted();

}
```

or this?

```java
@ExtendWith(EasyRandomExtension.class)
class MyAwesomeSortAlgoTest {
	@EasyRandomed 
	private int[] ints;
	
	@Test
	public void testSortAlgorithm() {
	   // When
	   int[] sortedInts = myAwesomeSortAlgo.sort(ints);
	
	   // Then
	   assertThat(sortedInts).isSorted();
	
	}
}
```

No more `EasyRandom`, the beans can be taken as params or as fields.

_Note that there is already a JUnit5 extension for this which targets 3.9.0 of easy-random, at which point it was RandomBeans, but the EasyRandom has since changed group and artifact under which it identifies._

Here I'll outline how it can be easily implemented. At crux, we just need to have `EasyRandomExtension` to implement `ParameterResolver` to resolve parameter and `BeforeEachCallback` to resolve the marked by `EasyRandomed` annotation.

We'll start with the annotation:

```java
@Documented @Target({PARAMETER, FIELD}) @Retention(RUNTIME)  
public @interface EasyRandomed {}
```

_I'll be covering the customization of the generated objects later_

## Implementation of `EasyRandomExtension`

### 1. Resolving annotated parameters

To work with `ParameterResolver`, we need to implement two methods `supportsParameter` and `resolveParameter` sounds reasonable enough:

```java
@Override  
public boolean supportsParameter(
	ParameterContext parameterContext,
	...
) throws ... {
    return parameterContext.getParameter()
		.getAnnotation(EasyRandomed.class) != null;
}

@Override  
public Object resolveParameter(
	ParameterContext parameterContext,
	...
) throws ... {
    final Parameter parameter = parameterContext.getParameter();
	final Class<?> paramType = parameter.getType();
	return new EasyRandom().nextObject(paramType);
}
```

### 2. Resolving annotated fields

the `BeforeEachCallback` gives a nice place to do some pre-processing on the test instances.

```java
@Override  
public void beforeEach(ExtensionContext context) throws ... {
	List<Object> instances = context.getRequiredTestInstances()
		.getAllInstances();
    for (Object instance : instances) {
        for (Field field : instance.getClass().getFields()) {
            if (field.getAnnotation(EasyRandomed.class) != null) {
                final Class<?> fieldType = field.getType();
				final Object fieldValue = new EasyRandom()
					.nextObject(fieldType);
                FieldUtils.writeField(field, instance, fieldValue, true);
			 }
        }
    }
}
```

_`FieldUtils` is from Commons Lang3, which makes my life easier. I'm not going to discuss how a private field of class can be set. I'm not going down that rabbit hole (again!)_

## Case: I want to customize my object

Till now we didn't provide a way for the client to customize the easy random generation, so let us revisit the Annotation and make some tweaks:

```java
@Documented @Target({PARAMETER}) @Retention(RUNTIME)  
public @interface EasyRandomed {
	Class<? extends Supplier<EasyRandom>> value() 
		default DefaultEasyRandomSupplier.class;
}

public class DefaultEasyRandomSupplier implements Supplier<EasyRandom> {
	@Override
	public EasyRandom get() {
		return new EasyRandom();
	}
}
```

So if you want to customize, you can provide your own instance of `EasyRandom` for the use-case.

the above sorting example can be like this:

```java
...
̥̥@Test
public void testSortAlgorithm(@EasyRandomed(BigArrayProvider.class) int[] ints) {
   // When
   int[] sortedInts = myAwesomeSortAlgo.sort(ints);

   // Then
   assertThat(sortedInts).isSorted();

}
public static class BigArrayProvider implements Supplier<EasyRandom> {
	@Override
	public EasyRandom get() {
		return new EasyRandom(
			new EasyRandomParameters()
				.collectionSizeRange(30000, 300000); // 30K to 300K ints
		);
	}
}
...
```

Now, we just need to use the configured supplier to get the `EasyRandom` object in `EasyRandomExtension`, instead of generating them ourselves.
the only changes are in the `resolveParameter` and `beforeEach` methods.

`resolveParameter` becomes:

```java
@Override  
public Object resolveParameter(
	ParameterContext parameterContext,
	...
) throws ... {
    final Parameter parameter = parameterContext.getParameter();
	final Class<?> paramType = parameter.getType();
	final EasyRandomed ann = parameter.getAnnotation(EasyRandomed.class);
	return ann.value().newInstance().get().nextObject(paramType);
}
```

similarly, `beforeEach` becomes:

```java
@Override  
public void beforeEach(ExtensionContext context) throws ... {
	List<Object> instances = context.getRequiredTestInstances()
		.getAllInstances();
    for (Object instance : instances) {
        for (Field field : instance.getClass().getFields()) {
            if (field.getAnnotation(EasyRandomed.class) != null) {
                final Class<?> fieldType = field.getType();
				final EasyRandomed ann = 
					field.getAnnotation(EasyRandomed.class);
				final Object fieldValue = ann.value().newInstance().get()
					.nextObject(fieldType);
                FieldUtils.writeField(field, instance, fieldValue, true);
			 }
        }
    }
}
```

So what is the status of this project? It's a single file, and we wouldn't see it getting merged into EasyRandom. Why? as EasyRandom is not intentended to be used with just JUnit5/4, it can be used in any other unit testing framework for JVM.

## Explanations on some of the design choices
 Q: Why didn't you just provide me with an API similar to `RandomBeansExtension`?
 A: Sure we could've just over-engineered our extension to provide all the configurations of `EasyRandomPrameter`, but that would result in over-engineering. Also you can imagine what a nightmare it would be when a parameter from `EasyRandomParameter` is deprecated or a new one is introduced.
 Let's be honest, I am not looking forward to work on this small piece of code for rest of eternity, I have other priorities, and it is easier for the project maintenance to keep it simple. Also this way, you can just wash your hands off of all the responsibility with the API of `EasyRandom`. I am only interested in using `EasyRandom.nextObject(...)` method.