MutualRegistrationVerification

Mutual Registration Pattern verification

Just like the JavaBean property verifier, a similar verifier can be written for the mutual registration pattern. Such a verifier would need to know beforehand the nature of the relationships between the two classes, and the properties in the classes that are involved in the relationships.

The relationships between the two classes themselves can be of several types - you could have a one-to-one, one-to-many or a many-to-many relationship between two properties of two classes. Once this is known, the verifier can then perform operations on one side of the relationship and then assert for changes to be visible on the other side.

For instance, given the following classes ParentPropertyType and ChildPropertyType which implement the mutual registration pattern for the properties childPropertyName and parentPropertyName respectively.

class ParentPropertyType
{
    // {{ ChildPropertyName
    private ChildPropertyType childPropertyName;

    public ChildPropertyType getChildPropertyName()
    {
        return childPropertyName;
    }

    public void setChildPropertyName(final ChildPropertyType childPropertyName)
    {
        this.childPropertyName = childPropertyName;
    }
    
    public void modifyChildPropertyName(final ChildPropertyType childPropertyName)
    {
        ChildPropertyType currentChildPropertyName = getChildPropertyName();
        // check for no-op
        if (childPropertyName == null || childPropertyName.equals(currentChildPropertyName))
        {
            return;
        }
        // dissociate existing
        clearChildPropertyName();
        // associate new
        childPropertyName.setParentPropertyName(this);
        setChildPropertyName(childPropertyName);
        // additional business logic
        onModifyChildPropertyName(currentChildPropertyName, childPropertyName);
    }

    public void clearChildPropertyName()
    {
        ChildPropertyType currentChildPropertyName = getChildPropertyName();
        // check for no-op
        if (currentChildPropertyName == null)
        {
            return;
        }
        // dissociate existing
        currentChildPropertyName.setParentPropertyName(null);
        setChildPropertyName(null);
        // additional business logic
        onClearChildPropertyName(currentChildPropertyName);
    }
    
    protected void onModifyChildPropertyName(final ChildPropertyType oldX, final ChildPropertyType newX)
    {
    }

    protected void onClearChildPropertyName(final ChildPropertyType oldX)
    {
    }
    // }}

}

class ChildPropertyType
{
    
    // {{ ParentPropertyName
    private ParentPropertyType parentPropertyName;

    public ParentPropertyType getParentPropertyName()
    {
        return parentPropertyName;
    }

    public void setParentPropertyName(final ParentPropertyType parentPropertyName)
    {
        this.parentPropertyName = parentPropertyName;
    }
    
    public void modifyParentPropertyName(final ParentPropertyType parentPropertyName)
    {
        ParentPropertyType currentParentPropertyName = getParentPropertyName();
        // check for no-op
        if (parentPropertyName == null || parentPropertyName.equals(currentParentPropertyName))
        {
            return;
        }
        // delegate to parent to associate
        parentPropertyName.modifyChildPropertyName(this);
        // additional business logic
        onModifyParentPropertyName(currentParentPropertyName, parentPropertyName);
    }

    public void clearPropertyName()
    {
        ParentPropertyType currentParentPropertyName = getParentPropertyName();
        // check for no-op
        if (currentParentPropertyName == null)
        {
            return;
        }
        // delegate to parent to dissociate
        currentParentPropertyName.clearChildPropertyName();
        // additional business logic
        onClearParentPropertyName(currentParentPropertyName);
    }

    protected void onModifyParentPropertyName(final ParentPropertyType oldY, final ParentPropertyType newY)
    {
    }

    protected void onClearParentPropertyName(final ParentPropertyType oldY)
    {
    }
    // }}
    
}

On invoking the modifyChildPropertyName method in a ParentPropertyType instance, the current ParentPropertyType instance will now have a relationship with the provided ChildPropertyType instance. Also, the provided ChildPropertyType instance will also be modified to have a relationship in the reverse direction, with the ParentPropertyType instance.

The same is true when the modifyParentPropertyName method is invoked on a ChildPropertyType instance. The existing links between a ParentPropertyType and a ChildPropertyType will be replaced with new links, to ensure referential integrity.

The methods are implemented in accordance with the templates provided by the Apache Isis project. The MutualRegistrationVerifier class will use this "inside knowledge" to verify the implementation of this pattern.

The MutualRegistrationVerifier

The class MutualRegistrationVerifier is based on a similar model to the BeanVerifier class used for verifying properties of JavaBeans. It provides the following methods as it's API:

  • static MutualRegistrationVerifier forContext(TestContext context, ITypeFactory typeFactory)
  • void verify()

The forContext method implements a factory method pattern, and returns a concrete instance of a MutualRegistrationVerifier. The choice of the concrete MutualRegistrationVerifier is decided by the TestContext provided to the method.

A TestContext contains information about the current relationship being tested. It stores information about the parent class and property, the linked child class and property, and the type of relationship (1:1, 1:M, M:M) that exists between the properties. It also stores information about the type of test to be executed for the given test execution.

The following concrete types exist for the MutualRegistrationVerifier:

  • CollectionAddNullVerifier to verify the behavior of a 1:M (or a M:M) relationship, when a null element is added to the collection on the 1-side.
  • CollectionAddElementVerifier to verify the behavior of a 1:M (or a M:M) relationship, when a new element is added to the collection on the 1-side.
  • CollectionAddDuplicateVerifier to verify the behavior of a 1:M (or a M:M) relationship, when a duplicate element is added to the collection on the 1-side.
  • CollectionRemoveNullVerifier to verify the behavior of a 1:M (or a M:M) relationship, when null is removed from the collection on the 1-side.
  • CollectionRemoveElementVerifier to verify the behavior of a 1:M (or a M:M) relationship, when an existing element is removed from the collection on the 1-side.
  • CollectionRemoveNonexistentElementVerifier to verify the behavior of a 1:M (or a M:M) relationship, when an non-existent element is removed from the collection on the 1-side.
  • PropertyModifyNullVerifier to verify the behavior of a 1:1 (or a M:1) relationship, when the value of the property is set to null.
  • PropertyModifyWithNewValueVerifier to verify the behavior of a 1:1 (or a M:1) relationship, when the value of the property is set to a new instance.
  • PropertyModifyWithCurrentVerifier to verify the behavior of a 1:1 (or a M:1) relationship, when the value of the property is set to the current value of the property.
  • PropertyClearNotNullVerifier to verify the behavior of a 1:1 (or a M:1) relationship, when the property is cleared (at the instant when it has a value).
  • PropertyClearNullVerifier to verify the behavior of a 1:1 (or a M:1) relationship, when the property is cleared (at the instant when it is null).

The concrete types are chosen at runtime, before verification is performed.

The verify method performs the actual verification process. It uses the info in the TestContext for the MutualRegistrationVerifier instance, to verify the behavior of the class-under-test (the parent type in a TestContext). For instance, if a TestContext specifies that a 1:1 relationship exists between a parent and a child type, and that the TestContext is contained within a PropertyModifyNullVerifier instance, then the verification will involve invoking the modifyXYZ method with a null argument followed by an eventual assertion to check if the property is not null (since null arguments will have no effect when the modifyXYZ method is invoked).

Using the MutualRegistrationVerifier

The MutualRegistrationVerifier is used in a manner similar to the BeanVerifier. The class is used in test classes that are executed with the JUnit 4 Parameterized test runner.

The data for each execution is usually prepared using the VerifierHelper class:

    
    @Parameters
    public static Collection<Object[]> data()
    {
        return VerifierHelper.fetchPopulatedContexts(contexts);
    }

The fetchPopulatedContexts method of VerifierHelper creates new TestContexts, from the existing metadata in the provided TestContexts. Note that, the number of TestContexts returned by this method is often larger than the provided TestContexts, since this method examines the types of the relationships in the TestContexts and creates new TestContexts with additional information, using this preliminary data.

The following snippet demonstrates how the preliminary set of TestContexts are prepared. It demonstrates how the metadata in the Album.user-User.albums and Album.photos-Photo.album relationships are represented in the AlbumMutualRegistrationVerifier class.

    private static final TestContext[] contexts = new TestContext[] {
            new TestContext(Album.class, "user", User.class, "albums", MANY_TO_ONE),
            new TestContext(Album.class, "photos", Photo.class, "album", ONE_TO_MANY) };

At runtime, the fetchPopulatedContexts method of VerifierHelper will use the above metadata to create new TestContexts. These new TestContexts would contain information about the type of test to execute for a particular TestContext.

For example, given the Album.user-User.albums relationship which is M:1, the single TestContext specified here will be exploded into five separate TestContexts, each associated with a single behavior to test. These five new TestContexts would correlate to the following behaviors:

  • Modify the Album.user field, specifying null
  • Modify the Album.user field, specifying a new User
  • Modify the Album.user field, specifying the same value as the current User associated with the Album,
  • Clear the Album.user field, with the current value of the field being null
  • Clear the Album.user field, with the current value of the field being some User

Different TestContexts would be created when Collections are used in the class under test. Note that the TestContext is for a directed link. A different TestContext must be created for testing the relationship from the other direction; this is done in a different test class (in this example, in the tests classes verifying the pattern for the User and Photo classes)

When the @Test method is eventually invoked as shown below, one of the above behaviors will be verified. The verification would require creation of the concrete MutualRegistrationVerifier, when the forContext method is invoked. The pattern is asserted through the invocation of the verify method on the MutualRegistrationVerifier.

    @Test
    public void testProperty() throws Exception
    {
        logger.info("Executing Test Context {}", testContext);
        MutualRegistrationVerifier verifier = MutualRegistrationVerifier.forContext(testContext, typeFactory);
        verifier.verify();
    }

Updated

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.