View Javadoc
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   *     &#64;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   * &#64;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 }