Annotation Interface ProvideMatchers


@Documented @Retention(SOURCE) @Target(TYPE) @Inherited public @interface ProvideMatchers
This annotation can be used on a java class, to mark this class as supporting generation of hamcrest matcher.

This annotation is not supported on interface and enum. A error will be generated in this case.

This annotation is processed by an annotation processor, in order to generate :

  • One class for each annotated classes, that will contains Hamcrest Matchers for the class.
  • In case the annotation processor parameter "ch.powerunit.extensions.matchers.provideprocessor.ProvidesMatchersAnnotationsProcessor.factory" is set, this value define the fully qualified name of a interface that will be generated and will contains all start method allowing to create instance of the various matchers.

The generated classes are related with the hamcrest framework ; This library will be required in the classpath in order to compile or run the generated classes.

Concept regarding the generated Matchers

Hamcrest Matchers can be used, for example, with test framework (JUnit, PowerUnit, etc.) to validate expectation on object. Hamcrest provides several matchers to validate some information of an object (is an instance of, is, an array contains some value, etc.), but can't provide ready to use matcher for your own object. When trying to validate properties of object, no syntaxic sugar (ie. autocompletion) are available and only the generic method can be used.

With this annotation, it is possible to provide builder-like method, based on hamcrest, to validate fields of an object. To do so, the annotation processor do the following :

  • For each public field or public method starting with get or is without any argument, generated a private matcher based on the org.hamcrest.FeatureMatcher for this field ; this will provide a way to validate the value of one specific property.
  • Generate an interface and the related implementation of a matcher (which is also a builder) on the annotated classes itself, which will validate all of the properties.
  • Generate various methods, with a name based on the annotated class, to start the creation of the matcher.
The annotation processor will also generate javadoc and try to retrieve from the javadoc of the annotated element the information regarding generic attribute.

The processor will also try, for field which are not based on generics, to link the generated matchers between them, in order to provide chaining of the Fields.

First example

Let's assume the following class, containing one single field, will be processed by the annotation processor :

 package ch.powerunit.extensions.matchers.samples;

 import ch.powerunit.extensions.matchers.ProvideMatchers;

 @ProvideMatchers
 public class SimplePojo {
        public String oneField;
 }
 
In this case a class named SimplePojoMatchers will be generated. As a public interface, the following methods will be available :
  • public static SimplePojoMatcher simplePojoWith(): This will return a matcher (see below), which by default matches any instance of the SimplePojo class.
  • public static SimplePojoMatcher simplePojoWithSameValue(SimplePojo other) : This will return a matcher, which by default matches an instance of the SimplePojo having the field oneField matching (Matcher is of hamcrest) of the reference object.
The returned interface is already a correct hamcrest matcher. This interface provide method that set the expectating on the various fields. As in this case, where is only one field, the returned interface ensure that once the expected is defined, it is not possible to modify it. Depending of the type of the field, various methods are generated to define the expectation :
  • Two standards methods are defined for all type of fields : Matcher <SimplePojo> oneField(Matcher<? super java.lang.String> matcher) and Matcher<SimplePojo> oneField(String value). The second one is a shortcut to validate the field with the is Matcher and the first one accept another matcher ; The method with matcher parameter ensures that it is possible to combine any other matcher provided by hamcrest or any others extensions.
  • As the field is a String, others special expectation (shortcut) are provided, for example : oneFieldComparesEqualTo, oneFieldLessThan, oneFieldStartsWith, etc.
Second example

In case the annotated contains several fields, the generated DSL provide chained methods, for example TwoFieldsPojoMatcher firstField(Matcher<? super String> matcher) and TwoFieldsPojoMatcher secondField(Matcher<? super String> matcher). Also, depending on the class, other with methods may be provided.

Usage example

Assuming powerunit as a test framework, the usage of the matcher will look like :

 @Test
 public void testOKMatcherWithComparable() {
        Pojo1 p = new Pojo1();
        p.msg2 = "12";
        assertThat(p).is(Pojo1Matchers.pojo1With().msg2ComparesEqualTo("12"));
 }
 
 
Assuming the msg2 is change to the value 11, the resulting unit test error will look like (the Pojo1 classes contains several fields) :
 expecting an instance of ch.powerunit.extensions.matchers.samples.Pojo1 with
 [msg2 a value equal to "12"]
 [msg3 ANYTHING]
 [msg4 ANYTHING]
 [msg5 ANYTHING]
 [msg6 ANYTHING]
 [msg7 ANYTHING]
 [msg8 ANYTHING]
 [msg9 ANYTHING]
 [msg12 ANYTHING]
 [msg1 ANYTHING]
 [myBoolean ANYTHING]
 [oneBoolean ANYTHING]
 but [msg2 "11" was less than "12"]
 
 

Overriding the way the matchers are generated

  • The attribute matchersClassName may be used to change the simple name (NOT THE FULLY QUALIFIED NAME) of the generated class.
  • The attribute matchersPackageName may be used to change the package name of the generated class.

Extensions

The framework, since version 0.1.0, is able to detect others library and use it :


Linking between matchers

The annotation processor try to link the generated matchers. The goal is that when a field or the super class also have a matchers, this one is used. It is not always possible to link everything but the concept is the following :

  • If the super class is compiled at the same time and is annotated with this annotation, the annotation processor may add a control on the parent and generated chaining method.
  • If a field class is compiled at the same time and is annotated with this annotation, the annotation processor may add chaining and dedicated control that use this matcher.
  • If a matcher, following the convention of this framework is detected for the super class or a field (already compiled for example), the annotation processor may also apply the previous rules by using this detected matcher.
  • The annotation processor may also try to find matcher for element of Map, List, Array.

Ignore field in WithSameValue matchers

Since version 0.3.0, it is possible to pass a list of field names to be ignored when constructing a WithSameValue matcher. The generated matcher tries to also apply in cascade this ignore feature to included object. The syntax to ignore fields of another fields is to use fieldName.fieldNameInside.

The generated matcher may not be able to apply the ignore feature in all the included fields of the fields.


Cycle detection

Since version 0.3.0, the generated WithSameValue matchers try to detect the cycle in the object to be compared. Cycle may be included for example when working with bidirectional link. The matcher is not able to detect all cycles, but when detected, replace the control by a same instance control.

Author:
borettim
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    boolean
    This attribute may be used in case when it is not possible to create a sure WithSameValue matcher.
    This attribute may be used to set a comments that will be passed inside the comments attribute of the @Generated annotation.
    boolean
    May be used to disable generation of the factory.
    This attribute may be used to enable some extensions for this objects.
    This attribute may be used to override the default class name that will contains the generated matchers.
    This attribute may be used to override the default package name that will be used for the generated matchers.
    This attribute may be used to generate additional exposition methods for the object.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    May be use as part of extensions() to enable, if available, an extension an each String to be validated by using a json validation.
  • Field Details

    • JSON_EXTENSION

      static final String JSON_EXTENSION
      May be use as part of extensions() to enable, if available, an extension an each String to be validated by using a json validation.
      Since:
      0.1.0
      See Also:
  • Element Details

    • matchersClassName

      String matchersClassName
      This attribute may be used to override the default class name that will contains the generated matchers.

      By default, this attribute is an empty string, which indicate to use the default construction pattern.

      By default, the Matchers class name is the name of the annotated class, followed by Matchers.

      Using this attribute may make the matcher undetectable when working with separated compilation.

      Returns:
      the name of the matchers class or an empty string if this is not overloaded.
      Default:
      ""
    • matchersPackageName

      String matchersPackageName
      This attribute may be used to override the default package name that will be used for the generated matchers.

      By default, this attribute is an empty string, which indicate to use the default construction pattern.

      By default, the Matchers package name is the same that the annotated class.

      Using this attribute may make the matcher undetectable when working with separated compilation.

      Returns:
      the name of the matchers package or an empty string if this is not overloaded.
      Default:
      ""
    • comments

      String comments
      This attribute may be used to set a comments that will be passed inside the comments attribute of the @Generated annotation.
      Returns:
      the comments or an empty string if ignored.
      Default:
      ""
    • moreMethod

      This attribute may be used to generate additional exposition methods for the object.

      By default, only the standard method are generated.

      Returns:
      additional method to be generated or an empty array by default.
      Since:
      0.1.0
      Default:
      {}
    • extensions

      String[] extensions
      This attribute may be used to enable some extensions for this objects.

      By default, no extension are enabled.

      Returns:
      the extension to be used or an empty array by default.
      Since:
      0.1.0
      See Also:
      Default:
      {}
    • allowWeakWithSameValue

      boolean allowWeakWithSameValue
      This attribute may be used in case when it is not possible to create a sure WithSameValue matcher. This may be the case when the annotated class extends a not annotated class or when the parent class is already compiled.

      By default, this mode is set to false.

      In case this option is set to true and the WithSameValue matcher is considered as weak, a warning will be produced.

      Returns:
      to create (or not) a WithSameValue matcher even if it is not possible to ensure the control of the field of the parent class.
      Since:
      2.0.0
      Default:
      false
    • disableGenerationOfFactory

      boolean disableGenerationOfFactory
      May be used to disable generation of the factory.
      Returns:
      true to disable the generation of the factory
      Since:
      0.4.0
      Default:
      false