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}