https://github.com/c0stra/fluent-api-dry-run
Reflection proxy based dry run of complex fluent sentences (statements)
https://github.com/c0stra/fluent-api-dry-run
Last synced: 13 days ago
JSON representation
Reflection proxy based dry run of complex fluent sentences (statements)
- Host: GitHub
- URL: https://github.com/c0stra/fluent-api-dry-run
- Owner: c0stra
- License: bsd-2-clause
- Created: 2021-04-28T15:50:56.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2021-05-25T15:59:33.000Z (about 5 years ago)
- Last Synced: 2025-10-20T10:41:35.636Z (9 months ago)
- Language: Java
- Size: 47.9 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Fluent API dry run
[](https://search.maven.org/#search%7Cga%7C1%7Cfluent-api-dry-run)
[](https://travis-ci.org/c0stra/fluent-api-dry-run)
Reflection proxy based dry run of complex fluent sentences (statements).
It's not really about mocking in terms of testing, but rather allowing to
bypass the real functionality normally provided by existing implementations.
Compared to normal situations, where providing some simple alternative implementation
is about one/two classes, with fluent API this task gets much more complex.
The problem is, that any method, at the end returning `null` introduces a risk
of `NullPointerException` further in the fluent method chain. So with fluent API, we need to avoid
returning `null` even in cases, when we want to "bypass" the implementation.
Imagine following code:
```java
user.opens("http://test-application/")
.enters().login("user123").password("s3cr3t")
.andSubmits();
```
When e.g. we just provide simple bypassing implementation (or mock like mockito) of the `user`,
where method `opens()` returns null, we get `NullPointerException` when method `enters()` should be invoked.
Therefore a solution provided in this module, is reflection proxy based, automatically propagating
mechanism, which is avoiding this risk.
### 1. Maven configuration
In order to have this feature available, you need to add following maven dependency:
```xml
foundation.fluent.api
fluent-api-dry-run
1.2
```
### 2. Usage
You get the dry run "implementation" of some fluent API root by following initialization:
```java
User user = DryRun.create("My dry run", User.class);
```
In turn you can use the fluent api of `User` and it won't throw any `NullPointerException`:
```java
user.opens("http://test-application/")
.enters().login("user123").password("s3cr3t")
.andSubmits();
```
### 3. Default instances
This tool can only propagate itself on interfaces. But it provides simple workaround for non-interface return types.
You can provide set of default values. If a any default value can be assigned to current return type, it will be returned.
Only if none matches, then for proxy the propagation will take place, and for non-proxy an exception is thrown, so the
point of failure is clear, avoiding `NPE` somewhere in complex chain, where it's impossible to find
out for which method it got thrown.
So in fact even interface return types can be instructed to return default value instead of performing
the auto-propagation.
There is implicit set of default values, which is always present for primitive types.
Additional ones can be provided by additional vararg parameters of the `dryRun` method.
### 4. Method invocation callback
The implementation allows to subscribe for individual method calls using a callback.
You can provide additional parameter `MethodCallEventHandler` function interface, and this one will receive
all method calls in the chain.
### 5. Reflection and generics
Normally generics are referred as compile time only feature in Java. It's not completely
true. The reflection API contains options to get also generic information about types in some
situations.
E.g.
```java
private final List list;
```
In the code above, the generic information is not available. The `T` is just a parameter.
But in this case:
```java
private final List list;
```
even the Java reflection allows us to get the information. In that case
the `Field.getGenericType()` method returns `ParametrizedType` representation, which contains
actual type parameters.
Having said that, we can overcome the standard limitations of runtime type erasure, and still
get / resolve actual type parameters in generic types, under some circumstances.
The current limitation simply is, that the interface representing our entry point in the fluent API
(the class, provided to the factory, e.g. `User.class`) needs not to take any generic parameters.
But any inherited interfaces can.
Second limitation is of course on generic methods. Those cannot be resolved.