001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.test.valueobjects;
017
018import static de.cuioss.tools.collect.CollectionLiterals.immutableList;
019import static org.junit.jupiter.api.Assertions.assertNotNull;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Set;
025
026import org.junit.jupiter.api.BeforeEach;
027import org.junit.jupiter.api.Test;
028
029import de.cuioss.test.generator.TypedGenerator;
030import de.cuioss.test.valueobjects.api.ObjectContractTestSupport;
031import de.cuioss.test.valueobjects.api.TestContract;
032import de.cuioss.test.valueobjects.api.contracts.VerifyBeanProperty;
033import de.cuioss.test.valueobjects.api.contracts.VerifyFactoryMethod;
034import de.cuioss.test.valueobjects.api.object.ObjectTestConfig;
035import de.cuioss.test.valueobjects.api.object.ObjectTestContracts;
036import de.cuioss.test.valueobjects.api.object.VetoObjectTestContract;
037import de.cuioss.test.valueobjects.contract.ContractRegistry;
038import de.cuioss.test.valueobjects.objects.ParameterizedInstantiator;
039import de.cuioss.test.valueobjects.objects.impl.AbstractInlineInstantiator;
040import de.cuioss.test.valueobjects.property.PropertyMetadata;
041import de.cuioss.test.valueobjects.util.ObjectContractHelper;
042import lombok.AccessLevel;
043import lombok.Getter;
044
045/**
046 * Base-class for running tests on value-objects. It runs two type of tests:
047 * <ul>
048 * <li>Tests for the canonical {@link Object} methods
049 * {@link Object#equals(Object)}, {@link Object#hashCode()} and
050 * {@link Object#toString()} with each test can be vetoed using
051 * {@link VetoObjectTestContract} at type-level. See
052 * {@link de.cuioss.test.valueobjects.api.object} for details</li>
053 * <li>Individual contract-testing like {@link VerifyBeanProperty} or
054 * {@link VerifyFactoryMethod}, see
055 * {@link de.cuioss.test.valueobjects.api.contracts} for details.</li>
056 * </ul>
057 * <h2>Configuration</h2>
058 * <p>
059 * See {@link PropertyAwareTest} for details on configuring
060 * {@link PropertyMetadata} and {@link TypedGenerator}
061 * </p>
062 * Usage examples can be found at the package-documentation:
063 * {@link de.cuioss.test.valueobjects.junit5}
064 *
065 * @author Oliver Wolff
066 * @param <T> identifying the type to be tested is usually but not necessarily
067 *            at least {@link Serializable}.
068 */
069public class ValueObjectTest<T> extends PropertyAwareTest<T> implements ObjectContractTestSupport {
070
071    /** The active object-contracts to be tested */
072    private Set<ObjectTestContracts> activeObjectContracts;
073
074    /**
075     * Needed {@link ParameterizedInstantiator} for creating test Objects or
076     * {@link #shouldImplementObjectContracts()}
077     */
078    @Getter(AccessLevel.PROTECTED)
079    private List<ParameterizedInstantiator<T>> objectContractInstantiator;
080
081    @Getter
082    private List<TestContract<T>> testContracts;
083
084    /**
085     * Initializes all contracts
086     */
087    @BeforeEach
088    public void initializeBaseClass() {
089        activeObjectContracts = ObjectContractHelper.handleVetoedContracts(getClass());
090
091        testContracts = resolveTestContracts(getPropertyMetadata());
092
093        objectContractInstantiator = new ArrayList<>();
094        testContracts.forEach(contract -> objectContractInstantiator.add(contract.getInstantiator()));
095    }
096
097    /**
098     * Resolves the concrete {@link TestContract}s to be tested. They are derived by
099     * the corresponding annotations
100     *
101     * @param initialMetadata
102     * @return
103     */
104    protected List<TestContract<T>> resolveTestContracts(final List<PropertyMetadata> initialMetadata) {
105        return ContractRegistry.resolveTestContracts(getTargetBeanClass(), getClass(), initialMetadata);
106    }
107
108    @Override
109    @Test
110    public void shouldImplementObjectContracts() {
111        var instantiators = getObjectContractInstantiator();
112        if (instantiators.isEmpty()) {
113            assertNotNull(anyValueObject(),
114                    "You need to configure either at least one de.cuioss.test.valueobjects.api.contracts or implement #anyValueObject()");
115            instantiators = immutableList(new AbstractInlineInstantiator<>() {
116
117                @Override
118                protected T any() {
119                    return anyValueObject();
120                }
121            });
122        }
123        final var objectTestConfig = this.getClass().getAnnotation(ObjectTestConfig.class);
124        for (final ParameterizedInstantiator<T> instantiator : instantiators) {
125            for (final ObjectTestContracts objectTestContracts : activeObjectContracts) {
126                objectTestContracts.newObjectTestInstance().assertContract(instantiator, objectTestConfig);
127            }
128        }
129    }
130
131    /**
132     * <p>
133     * Tests all configured {@link TestContract}s. The individual contracts are to
134     * be configured using class level annotations or overwriting
135     * resolveTestContracts(SortedSet)
136     * </p>
137     */
138    @Test
139    public final void shouldVerifyTestContracts() {
140        for (final TestContract<T> contract : getTestContracts()) {
141            contract.assertContract();
142        }
143    }
144
145    /**
146     * This method can be used in two ways:
147     * <ul>
148     * <li>In case you have at least de.cuioss.test.valueobjects.api.contracts
149     * configured this method will implicitly return an arbitrary {@link Object} of
150     * the implicitly created underlying {@link ParameterizedInstantiator}</li>
151     * <li>In case you have no {@link ParameterizedInstantiator} configured you can
152     * implement this method for feeding the
153     * {@link #shouldImplementObjectContracts()}</li>
154     * </ul>
155     *
156     * @return a concrete valueObject, or null
157     */
158    protected T anyValueObject() {
159        if (!getObjectContractInstantiator().isEmpty()) {
160            return getObjectContractInstantiator().iterator().next().newInstanceFull();
161        }
162        return null;
163    }
164}