Repository: serpro69/kotlin-faker (kotlin)

Issue:
Add support for internal constructors in randomClassInstance
Currently doing something like 

```kotlin
public data class SomeClass internal constructor(val s: String)
val a = randomClassInstance<SomeClass>()

```

will result in

```
No suitable constructor or predefined instance found for class SomeClass
java.util.NoSuchElementException: No suitable constructor or predefined instance found for class 
SomeClass
	at io.github.serpro69.kfaker.provider.misc.RandomClassProvider.randomClassInstance(RandomClassProvider.kt:119)
```

Supporting internally-visible constructors seems like a good idea and should be added.

> Suggested by @timrijckaert

Pull request:
Closes #263

Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 98e1cd31e5..d86234f82e 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -11,6 +11,7 @@
 [discrete]
 === Added
 
+* https://github.com/serpro69/kotlin-faker/pull/266[#266] (:core) Add support for internal constructors in randomClassInstance
 * https://github.com/serpro69/kotlin-faker/pull/260[#260] (:core) Add config strategy for default values of RandomClass instances
 * https://github.com/serpro69/kotlin-faker/pull/258[#258] (:core) Add RandomClassProvider::randomClassInstance fun with KClass input
 * https://github.com/serpro69/kotlin-faker/pull/254[#254] (:faker) Add Pictures faker
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt
index 0aacc21115..55eecad6a0 100644
--- a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProvider.kt
@@ -5,22 +5,17 @@ import io.github.serpro69.kfaker.RandomService
 import io.github.serpro69.kfaker.provider.misc.ConstructorFilterStrategy.MAX_NUM_OF_ARGS
 import io.github.serpro69.kfaker.provider.misc.ConstructorFilterStrategy.MIN_NUM_OF_ARGS
 import io.github.serpro69.kfaker.provider.misc.ConstructorFilterStrategy.NO_ARGS
-import io.github.serpro69.kfaker.provider.misc.DefaultValuesStrategy.*
+import io.github.serpro69.kfaker.provider.misc.DefaultValuesStrategy.ALL_RANDOM
+import io.github.serpro69.kfaker.provider.misc.DefaultValuesStrategy.PICK_RANDOMLY
+import io.github.serpro69.kfaker.provider.misc.DefaultValuesStrategy.USE_DEFAULTS
 import io.github.serpro69.kfaker.provider.misc.FallbackStrategy.FAIL_IF_NOT_FOUND
 import io.github.serpro69.kfaker.provider.misc.FallbackStrategy.USE_MAX_NUM_OF_ARGS
 import io.github.serpro69.kfaker.provider.misc.FallbackStrategy.USE_MIN_NUM_OF_ARGS
-import kotlin.Boolean
-import kotlin.Char
-import kotlin.Double
-import kotlin.Float
-import kotlin.Int
-import kotlin.Long
-import kotlin.Short
-import kotlin.String
 import kotlin.reflect.KClass
 import kotlin.reflect.KParameter
 import kotlin.reflect.KType
-import kotlin.reflect.KVisibility
+import kotlin.reflect.KVisibility.INTERNAL
+import kotlin.reflect.KVisibility.PUBLIC
 import kotlin.reflect.full.starProjectedType
 import kotlin.reflect.jvm.jvmName
 
@@ -88,20 +83,20 @@ class RandomClassProvider {
     fun reset() = config.reset()
 
     /**
-     * Creates an instance of [T]. If [T] has a parameterless public constructor then it will be used to create an instance of this class,
+     * Creates an instance of [T]. If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
      * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
      *
-     * @throws NoSuchElementException if [T] has no public constructor.
+     * @throws NoSuchElementException if [T] has no public or internal constructor.
      */
     inline fun <reified T : Any> randomClassInstance() = T::class.randomClassInstance(config)
 
     /**
-     * Creates an instance of [T]. If [T] has a parameterless public constructor then it will be used to create an instance of this class,
+     * Creates an instance of [T]. If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
      * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
      *
      * @param configurator configure instance creation.
      *
-     * @throws NoSuchElementException if [T] has no public constructor.
+     * @throws NoSuchElementException if [T] has no public or internal constructor.
      */
     inline fun <reified T : Any> randomClassInstance(configurator: RandomProviderConfig.() -> Unit): T {
         return T::class.randomClassInstance(RandomProviderConfig().apply(configurator))
@@ -109,12 +104,12 @@ class RandomClassProvider {
 
     /**
      * Creates an instance of [T] from the [KClass] input.
-     * If [T] has a parameterless public constructor then it will be used to create an instance of this class,
+     * If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
      * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
      *
      * @param kClass a [KClass] of [T]
      *
-     * @throws NoSuchElementException if [T] has no public constructor.
+     * @throws NoSuchElementException if [T] has no public or internal constructor.
      */
     fun <T : Any> randomClassInstance(kClass: KClass<T>): T {
         return kClass.randomClassInstance(config)
@@ -122,13 +117,13 @@ class RandomClassProvider {
 
     /**
      * Creates an instance of [T] from the [KClass] input.
-     * If [T] has a parameterless public constructor then it will be used to create an instance of this class,
+     * If [T] has a parameterless public/internal constructor then it will be used to create an instance of this class,
      * otherwise a constructor with minimal number of parameters will be used with randomly-generated values.
      *
      * @param kClass a [KClass] of [T]
      * @param configurator configure instance creation.
      *
-     * @throws NoSuchElementException if [T] has no public constructor.
+     * @throws NoSuchElementException if [T] has no public or internal constructor.
      */
     fun <T : Any> randomClassInstance(kClass: KClass<T>, configurator: RandomProviderConfig.() -> Unit): T {
         return kClass.randomClassInstance(RandomProviderConfig().apply(configurator))
@@ -144,7 +139,10 @@ class RandomClassProvider {
         val defaultInstance: T? by lazy {
             if (config.constructorParamSize == -1 && config.constructorFilterStrategy == NO_ARGS) {
                 randomPrimitiveOrNull() as T? ?: try {
-                    constructors.firstOrNull { it.parameters.isEmpty() && it.visibility == KVisibility.PUBLIC }?.call()
+                    constructors.firstOrNull {
+                        it.parameters.isEmpty() &&
+                            (it.visibility == PUBLIC || it.visibility == INTERNAL)
+                    }?.call()
                 } catch (e: Exception) {
                     throw InstantiationException("Failed to instantiate $this")
                         .initCause(e)
@@ -174,7 +172,9 @@ class RandomClassProvider {
         if (this.java.isPrimitive) return randomPrimitiveOrNull() as T
 
         return objectInstance ?: defaultInstance ?: run {
-            val constructors = constructors.filter { it.visibility == KVisibility.PUBLIC }
+            val constructors = constructors.filter {
+                it.visibility == PUBLIC || it.visibility == INTERNAL
+            }
 
             val constructor = constructors.firstOrNull {
                 it.parameters.size == config.constructorParamSize
diff --git a/docs/src/orchid/resources/wiki/extras.md b/docs/src/orchid/resources/wiki/extras.md
index 5e1af9fb0b..3ad4876db3 100644
--- a/docs/src/orchid/resources/wiki/extras.md
+++ b/docs/src/orchid/resources/wiki/extras.md
@@ -31,7 +31,8 @@ It is possible to create a random instance of (almost) any class.
 
 There are some rules to keep in mind:
 
-- By default, the constructor with the least number of arguments is used (This can be configured - read on.)
+- By default, a constructor with the least number of arguments is used (This can be configured - read on.)
+  - Both publicly- and internally-visible constructors are supported
 - `kolin.Array` type in the constructor is not supported at the moment
 - Inner classes (either direct generation or as class parameter type) are not supported at the moment
 
@@ -192,7 +193,7 @@ val person: Person = faker.randomProvider.randomClassInstance {
 
 <br>
 
-#### Pre-defined instance for classes with no public constructors
+#### Pre-defined instance for classes with no public or internal constructors
 
 By default, `randomClassInstance` can't generate classes with no public constructors, but this can be worked around by using `typeGenerator`:
 
@@ -243,8 +244,8 @@ A random class instance will be generated using the following precedence rules:
 
 - object instance
 - "default instance" of a class
-  - uses the public constructor with the least number of arguments, unless otherwise configured (see [Random Class Instance Configuration]({{ link(collectionType='wiki', collectionId='', itemId='Extras') }}#random-class-instance-configuration))
-- "predefined instance" of a class if no public constructors are found
+  - uses a public/internal constructor with the least number of arguments, unless otherwise configured (see [Random Class Instance Configuration]({{ link(collectionType='wiki', collectionId='', itemId='Extras') }}#random-class-instance-configuration))
+- "predefined instance" of a class if no public or internal constructors are found
 - failing all of the above, `NoSuchElementException` will be thrown
 
 {% btc %}{% endbtc %}


Test patch:
diff --git a/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt b/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt
index df1fa2434b..7bee37936e 100644
--- a/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt
+++ b/core/src/test/kotlin/io/github/serpro69/kfaker/provider/misc/RandomClassProviderTest.kt
@@ -190,7 +190,20 @@ class RandomClassProviderTest : DescribeSpec({
         }
     }
 
-    describe("no public constructors") {
+    describe("internal constructor") {
+        it("should create an instance with a default no-argument constructor") {
+            val t = randomProvider.randomClassInstance<TestClassInternal>()
+            t.id shouldBe 42
+        }
+        it("should use a non-default constructor") {
+            val t = randomProvider.randomClassInstance<TestClassInternal>() {
+                this.constructorParamSize = 1
+            }
+            t.id shouldNotBe 42
+        }
+    }
+
+    describe("no public or internal constructors") {
         context("creating a random instance of a class") {
             it("should return a predefined instance via typeGenerator") {
                 val testClassMin = randomProvider.randomClassInstance<TestClassNoPublic> {
@@ -920,6 +933,15 @@ class Go(val name: String) : TestSealedCls() {
 
 object TestObject
 
+class TestClassInternal internal constructor() {
+    var id = 42
+        private set
+
+    internal constructor(id: Int) : this() {
+        this.id = id
+    }
+}
+
 class TestClassNoPublic private constructor(val id: Int) {
     companion object {
         val MIN = TestClassNoPublic(Int.MIN_VALUE)