diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1db8648..725db1877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Support records that don't allow 0 values in their fields, with Warning.ZERO_FIELDS. ([Issue 613](https://github.com/jqno/equalsverifier/issues/613)) ## [3.9.1] - 2022-03-19 ### Fixed diff --git a/docs/_errormessages/record-failed-to-invoke-constructor.md b/docs/_errormessages/record-failed-to-invoke-constructor.md new file mode 100644 index 000000000..e606415a5 --- /dev/null +++ b/docs/_errormessages/record-failed-to-invoke-constructor.md @@ -0,0 +1,32 @@ +--- +title: Record: failed to invoke constructor +--- + Record: failed to invoke constructor + If the record does not accept 0 as a value for its fields, consider providing valid prefab values for those fields and suppressing Warning.ZERO_FIELDS. + +Sometimes, a constructor needs to validate its input, throwing an exception on certain values. This can lead to problems when the value `0` is not allowed, and the constructor is part of a record instead of a regular class, like this: + +{% highlight java %} +public record Foo(int i) { + public Foo { + if (i < 42) { + throw new IllegalArgumentException(); + } + } +} +{% endhighlight %} + +One of the things EqualsVerifier does, is to run checks with fields set to their default values. For reference types, the default value is `null`, which has its own [chapter in the manual](/equalsverifier/manual/null). For primitive types, the default value is `0` (or `0.0`, `\u0000`, `false`). In regular classes, EqualsVerifier bypasses the constructor, so the exception can never be thrown. However, reflection support is much more limited for records, and their constructors cannot be bypassed. + +Therefore, we need to signal EqualsVerifier to skip the checks with default values, by suppressing `Warning.ZERO_FIELDS`. + +Another thing EqualsVerifier does, is to run checks with fields set to certain prefab values. For integral types, these values are `1` and `2`. If these values are not allowed by the record's constructor either, we need to provide new prefab values as well. + +In these cases, the call to EqualsVerifier will look like this: + +{% highlight java %} +EqualsVerifier.forClass(Foo.class) + .suppress(Warning.ZERO_FIELDS) + .withPrefabValues(int.class, 42, 1337) + .verify(); +{% endhighlight %} diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java index 8d93ba085..9deaf7010 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/RecordObjectAccessor.java @@ -141,7 +141,7 @@ private T callRecordConstructor(List params) { () -> constructor.newInstance(params.toArray(new Object[0])), "Record: failed to invoke constructor.\n" + " If the record does not accept 0 as a value for its fields," + - " consider providing valid prefab values for those fields and suppressing Warning.ZERO_FIELDS" + " consider providing valid prefab values for those fields and suppressing Warning.ZERO_FIELDS." ); } }