001/** 002 * The MIT License (MIT) 003 * 004 * Copyright (c) 2017 tools4j.org (Marco Terzer) 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in all 014 * copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024package org.tools4j.spockito; 025 026import org.junit.Test; 027import org.junit.runners.model.FrameworkField; 028import org.junit.runners.model.FrameworkMethod; 029import org.junit.runners.model.InitializationError; 030 031import java.lang.reflect.Constructor; 032import java.lang.reflect.Field; 033import java.util.ArrayList; 034import java.util.List; 035import java.util.Objects; 036 037/** 038 * A runner for the case of a single data row applied to a set of test methods. This case applies if the 039 * {@link org.tools4j.spockito.Spockito.Unroll} annotation is present at test class level. 040 */ 041public class SingleRowMultiTestRunner extends AbstractSpockitoTestRunner { 042 043 private final TableRow tableRow; 044 private final ValueConverter defaultValueConverter; 045 046 public SingleRowMultiTestRunner(final Class<?> clazz, 047 final TableRow tableRow, 048 final ValueConverter defaultValueConverter) throws InitializationError { 049 super(clazz); 050 this.tableRow = Objects.requireNonNull(tableRow); 051 this.defaultValueConverter = Objects.requireNonNull(defaultValueConverter); 052 validate(); 053 } 054 055 @Override 056 public Object createTest() throws Exception { 057 final Object testInstance = createTestUsingConstructorInjection(); 058 return fieldsAreAnnotated() ? injectAnnotatedFields(testInstance) : testInstance; 059 } 060 061 private Object createTestUsingConstructorInjection() throws Exception { 062 final Constructor<?> constructor = getTestClass().getOnlyConstructor(); 063 final ValueConverter valueConverter = Spockito.getValueConverter(constructor.getAnnotation(Spockito.UseValueConverter.class), defaultValueConverter); 064 final Object[] args = tableRow.convertValues(constructor, valueConverter); 065 return constructor.newInstance(args); 066 } 067 068 private Object injectAnnotatedFields(final Object testInstance) throws Exception { 069 final List<FrameworkField> fields = getFieldsAnnotatedByRef(); 070 final Object[] fieldValues = tableRow.convertValues(fields, defaultValueConverter); 071 for (int i = 0; i < fields.size(); i++) { 072 final Field field = fields.get(i).getField(); 073 try { 074 field.setAccessible(true); 075 field.set(testInstance, fieldValues[i]); 076 } catch (final Exception e) { 077 throw new Exception(getTestClass().getName() 078 + ": Trying to set " + field.getName() 079 + " with the value " + fieldValues[i], e); 080 } 081 } 082 return testInstance; 083 } 084 085 @Override 086 protected String getName() { 087 return Spockito.getName(getTestClass().getOnlyConstructor(), tableRow); 088 } 089 090 @Override 091 protected List<FrameworkMethod> computeTestMethods() { 092 final List<FrameworkMethod> testMethods = super.computeTestMethods(); 093 final List<FrameworkMethod> spockitoMethods = new ArrayList<>(testMethods.size()); 094 for (final FrameworkMethod testMethod : testMethods) { 095 if (testMethod.getMethod().getParameterCount() == 0) { 096 spockitoMethods.add(testMethod); 097 } else { 098 final Spockito.UseValueConverter useValueConverter = testMethod.getAnnotation(Spockito.UseValueConverter.class); 099 final ValueConverter methodValueConverter = Spockito.getValueConverter(useValueConverter, defaultValueConverter); 100 final UnrolledTestMethod spockitoTestMethod = new UnrolledTestMethod(testMethod.getMethod(), tableRow, methodValueConverter); 101 spockitoMethods.add(spockitoTestMethod); 102 } 103 } 104 return spockitoMethods; 105 } 106 107 108 @Override 109 protected String testName(final FrameworkMethod method) { 110 final String testName = super.testName(method); 111 //NOTE: we intentionally don't want class level Name annotation as a default here! 112 final Spockito.Name name = method.getAnnotation(Spockito.Name.class); 113 return name == null ? testName : testName + Spockito.getName(name, tableRow, ""); 114 } 115 116 @Override 117 protected void validateConstructor(List<Throwable> errors) { 118 validateOnlyOneConstructor(errors); 119 validateConstructorArgs(errors); 120 } 121 122 @Override 123 protected void validateFields(List<Throwable> errors) { 124 super.validateFields(errors); 125 if (fieldsAreAnnotated()) { 126 final List<FrameworkField> fields = getFieldsAnnotatedByRef(); 127 for (final FrameworkField field : fields) { 128 final String refName = field.getField().getAnnotation(Spockito.Ref.class).value(); 129 if (!tableRow.isValidRefName(refName)) { 130 errors.add(new Exception("Invalid @Ref value: " + refName + 131 " does not reference a column of the table defined by @Unroll")); 132 } 133 } 134 } 135 } 136 137 protected void validateConstructorArgs(List<Throwable> errors) { 138 final Constructor<?> constructor = getTestClass().getOnlyConstructor(); 139 final java.lang.reflect.Parameter[] parameters = constructor.getParameters(); 140 for (int i = 0; i < parameters.length; i++) { 141 final String refName = Spockito.parameterRefNameOrNull(parameters[i]); 142 if (refName != null && !tableRow.isValidRefName(refName)) { 143 errors.add(new Exception("Invalid @Ref value or parameter name for argument " + i + 144 " of type " + parameters[i].getType() + " in the constructor: " + refName + 145 " does not reference a column of the table defined by @Unroll")); 146 } 147 } 148 } 149 150 protected void validate() throws InitializationError { 151 final List<Throwable> errors = new ArrayList<>(); 152 super.collectInitializationErrors(errors); 153 } 154 155 @Override 156 protected void validateTestMethods(final List<Throwable> errors) { 157 final List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(Test.class); 158 for (final FrameworkMethod method : methods) { 159 method.validatePublicVoid(false, errors); 160 method.validateNoTypeParametersOnArgs(errors); 161 } 162 } 163 164 private List<FrameworkField> getFieldsAnnotatedByRef() { 165 return getTestClass().getAnnotatedFields(Spockito.Ref.class); 166 } 167 168 private boolean fieldsAreAnnotated() { 169 return !getFieldsAnnotatedByRef().isEmpty(); 170 } 171 172}