1 /* 2 * Copyright 2024 Adrian Herscu 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package dev.aherscu.qa.jgiven.commons.utils; 17 18 import static dev.aherscu.qa.testing.utils.ClassUtilsExtensions.*; 19 import static dev.aherscu.qa.testing.utils.StringUtilsExtensions.*; 20 21 import java.io.*; 22 import java.lang.reflect.*; 23 24 import org.testng.annotations.*; 25 26 import com.opencsv.bean.*; 27 28 import lombok.*; 29 import lombok.extern.slf4j.*; 30 31 /** 32 * Generic CSV data provider; you should specialize it for your type by 33 * overriding {@link #type()}, like this: 34 * 35 * <pre> 36 * public static final class FooCsvDataProvider 37 * extends AbstractCsvDataProvider { 38 * @Override 39 * protected Class<?> type() { 40 * return Foo.class; 41 * } 42 * } 43 * </pre> 44 * 45 * then you can use it like this: 46 * 47 * <pre> 48 * @Test(dataProviderClass = FooCsvDataProvider.class, dataProvider = AbstractCsvDataProvider.DATA) 49 * public void shouldReadFromCsv(final Foo value) {...} 50 * </pre> 51 * 52 * Only a single object parameter is supported. If your test method requires 53 * multiple parameters, then consider grouping them in a class instead. 54 * 55 * @author aherscu 56 */ 57 @NoArgsConstructor // data provider classes must have a no-args ctor 58 @Slf4j 59 // NOTE: cannot use generic types due to TestNG restriction 60 // java.lang.ClassCastException: class 61 // sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to 62 // class java.lang.Class 63 // (sun.reflect.generics.reflectiveObjects.TypeVariableImpl and java.lang.Class 64 // are in module java.base of loader 'bootstrap') 65 public abstract class AbstractCsvDataProvider { 66 public static final String DATA = "data"; 67 68 static String csvFileFor(final Method method) { 69 return method.getDeclaringClass().getSimpleName() 70 + DOT + method.getName() 71 + ".csv"; 72 } 73 74 @DataProvider(name = DATA) 75 @SneakyThrows 76 public Object[] data(final Method method) { 77 try (val csvReader = new InputStreamReader( 78 getRelativeResourceAsStream(method.getDeclaringClass(), 79 csvFileFor(method)))) { 80 return csvBuilderFor(csvReader) 81 .build() 82 .parse() 83 .toArray(); 84 } 85 } 86 87 /** 88 * @param csvReader 89 * the reader for associated CSV file 90 * @return iterator over deserialized CSV data 91 */ 92 protected CsvToBeanBuilder<Object> csvBuilderFor(final Reader csvReader) { 93 return new CsvToBeanBuilder<>(csvReader) 94 .withType(type()); 95 } 96 97 /** 98 * @return the type of data that should be deserialized 99 */ 100 abstract protected Class<?> type(); 101 }