I have this class ```class ImageToTextResponse pr...
# arrow
c
I have this class
Copy code
class ImageToTextResponse
private constructor(
    val errorId: ErrorId,
    val status: Status,
    val solution: ImageToTextSolution,
    val taskId: TaskId
) {
  companion object {
    operator fun invoke(obj: JsonObject): EitherNel<Throwable, ImageToTextResponse> {
      fun Raise<Throwable>.mkSolution(): ImageToTextSolution {
        val solutionJson = obj.getValue("solution").jsonObject
        val confidence = Confidence(solutionJson.getValue("confidence").toString()).bind()
        val text = CaptchaText(solutionJson.getValue("text").toString()).bind()

        return ImageToTextSolution(confidence, text)
      }

      return either {
        zipOrAccumulate(
            { ErrorId(obj.getValue("errorId").toString()).bind() },
            { Status(obj.getValue("status").toString()).bind() },
            { mkSolution() },
            { TaskId(obj.getValue("taskId").toString()).bind() }) { eid, status, solution, tid ->
              ImageToTextResponse(eid, status, solution, tid)
            }
      }
    }
  }
}
I wrote a
kotest
FunSpec
test for it.
Copy code
test("Missing errorId in Json gives Left of ImageToTextResponse") {
        Arb.bind(genMaybeErrorId, genMaybeStatus, genMaybeImageToTextSolution, genMaybeTaskId) { a, b, c, d -> Tuple4(a, b, c, d) }
          .forAll { (_, b, c, d) ->
            either {
              val status = b.bind()
              val solution = c.bind()
              val taskId = d.bind()

              buildJsonObject {
                put("status", Json.encodeToJsonElement(status.value))
                put("solution", Json.encodeToJsonElement(solution))
                put("taskId", Json.encodeToJsonElement(taskId.value))
              }
            }
              .mapLeft { nonEmptyListOf(Throwable()) }
              .flatMap { ImageToTextResponse(it) }
              .isLeft() shouldBe true
          }
      }
When I run the test, I get the error
Cannot create an instance of the class ImageToTextResponse. Specs must have a public zero-arg constructor.
I've created tests for other `value class`es in this way. What is different this time?
a
constructor is private
c
@Alejandro Serrano.Mena, I want the constructor to be private so that the
invoke
function is used instead.
a
I get that; I'm just pointing out that I think this is the reason why you get a problem running the test
c
Why does the constructor need to be public? My tested `value class`es also use the smart constructer (
invoke
) and the tests work fine.
Can you point me to documentation to explain this?
a
can you share the stack trace of the error?
c
@Alejandro Serrano.Mena, here you go
Copy code
io.kotest.engine.spec.SpecInstantiationException: Could not create instance of class com.revistek.captchasolver.capsolver.responses.ImageToTextResponse. Specs must have a public zero-arg constructor.
	at io.kotest.engine.spec.InstantiateSpecKt.javaReflectNewInstance(instantiateSpec.kt:46)
	at io.kotest.engine.spec.InstantiateSpecKt.createAndInitializeSpec(instantiateSpec.kt:30)
	at io.kotest.engine.spec.InstantiateSpecKt.instantiate(instantiateSpec.kt:11)
	at io.kotest.engine.spec.SpecRefKt.instance(SpecRef.kt:14)
	at io.kotest.engine.spec.SpecExecutor.createInstance-gIAlu-s(SpecExecutor.kt:63)
	at io.kotest.engine.spec.SpecExecutor.access$createInstance-gIAlu-s(SpecExecutor.kt:24)
	at io.kotest.engine.spec.SpecExecutor$execute$innerExecute$1.invokeSuspend(SpecExecutor.kt:40)
	at io.kotest.engine.spec.SpecExecutor$execute$innerExecute$1.invoke(SpecExecutor.kt)
	at io.kotest.engine.spec.SpecExecutor$execute$innerExecute$1.invoke(SpecExecutor.kt)
	at io.kotest.engine.spec.interceptor.ref.FinalizeSpecInterceptor.intercept-0E7RQCE(FinalizeSpecInterceptor.kt:24)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.BeforeSpecStateInterceptor.intercept-0E7RQCE(BeforeSpecStateInterceptor.kt:25)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.PrepareSpecInterceptor.intercept-0E7RQCE(PrepareSpecInterceptor.kt:25)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.ApplyExtensionsInterceptor.intercept-0E7RQCE(ApplyExtensionsInterceptor.kt:36)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.SpecFinishedInterceptor.intercept-0E7RQCE(SpecFinishedInterceptor.kt:22)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.SpecStartedInterceptor.intercept-0E7RQCE(SpecStartedInterceptor.kt:20)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.SpecRefExtensionInterceptor$intercept$inner$1.invokeSuspend(SpecRefExtensionInterceptor.kt:28)
	at io.kotest.engine.spec.interceptor.ref.SpecRefExtensionInterceptor$intercept$inner$1.invoke(SpecRefExtensionInterceptor.kt)
	at io.kotest.engine.spec.interceptor.ref.SpecRefExtensionInterceptor$intercept$inner$1.invoke(SpecRefExtensionInterceptor.kt)
	at io.kotest.engine.spec.interceptor.ref.SpecRefExtensionInterceptor.intercept-0E7RQCE(SpecRefExtensionInterceptor.kt:31)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.RequiresTagInterceptor.intercept-0E7RQCE(RequiresTagInterceptor.kt:35)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.TagsInterceptor.intercept-0E7RQCE(TagsInterceptor.kt:38)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.SystemPropertySpecFilterInterceptor.intercept-0E7RQCE(SystemPropertySpecFilterInterceptor.kt:48)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.SpecFilterInterceptor.intercept-0E7RQCE(SpecFilterInterceptor.kt:38)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.IgnoredSpecInterceptor.intercept-0E7RQCE(IgnoredSpecInterceptor.kt:51)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.EnabledIfInterceptor.intercept-0E7RQCE(EnabledIfInterceptor.kt:41)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.ref.RequiresPlatformInterceptor.intercept-0E7RQCE(RequiresPlatformInterceptor.kt:32)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invokeSuspend(SpecRefInterceptorPipeline.kt:49)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline$execute$3$1.invoke(SpecRefInterceptorPipeline.kt)
	at io.kotest.engine.spec.interceptor.SpecRefInterceptorPipeline.execute-0E7RQCE(SpecRefInterceptorPipeline.kt:50)
	at io.kotest.engine.spec.SpecExecutor.execute(SpecExecutor.kt:42)
	at io.kotest.engine.ConcurrentTestSuiteScheduler$schedule$8$1$1$2.invokeSuspend(ConcurrentTestSuiteScheduler.kt:72)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:277)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:48)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at io.kotest.common.RunBlockingKt.runBlocking(runBlocking.kt:3)
	at io.kotest.engine.launcher.MainKt.main(main.kt:34)
a
how do you define the generator
genMaybeImageToTextSolution
?
c
@Alejandro Serrano.Mena, here are my generators:
Copy code
object Generators {
  val genBlankString: Arb<String> = Arb.string(codepoints = Codepoint.whitespace())
  val genNonBlankString: Arb<String> = Arb.string().filter { it.isNotBlank() }
  val genNonDigitString: Arb<String> = Arb.stringPattern("^\\D*\$")

  val genMaybeConfidence: Arb<Either<InvalidConfidence, Confidence>> =
      Arb.double(0.5 + 1e-7, Double.POSITIVE_INFINITY).map { it.toString() }.map { Confidence(it) }
  val genMaybeCaptchaText: Arb<Either<InvalidCaptchaText, CaptchaText>> =
      genNonBlankString.map { CaptchaText(it) }
  val genMaybeImageToTextSolution: Arb<Either<Throwable, ImageToTextSolution>> =
      Arb.bind(genMaybeConfidence, genMaybeCaptchaText) { a, b ->
        a.flatMap { c -> b.map { t -> ImageToTextSolution(c, t) } }
      }
  val genMaybeErrorId: Arb<Either<InvalidErrorId, ErrorId>> =
      <http://Arb.int|Arb.int>().map { ErrorId(it.toString()) }
  val genMaybeTaskId: Arb<Either<InvalidTaskId, TaskId>> = genNonBlankString.map { TaskId(it) }
  val genMaybeStatus: Arb<Either<InvalidStatus, Status>> =
      Arb.enum<StatusValue>().map { Json.encodeToString(it) }.map { Status(it) }
  val genMaybeApiKey: Arb<Option<ApiKey>> = genNonBlankString.map { ApiKey(it) }
  val genMaybeAppId: Arb<Option<AppId>> = genNonBlankString.map { AppId(it) }
  val genTask = Arb.bind(Arb.enum<TaskType>(), Arb.string()) { a, b -> Task(a, b) }
}
The one you specifically asked for is this
Copy code
val genMaybeImageToTextSolution: Arb<Either<Throwable, ImageToTextSolution>> =
      Arb.bind(genMaybeConfidence, genMaybeCaptchaText) { a, b ->
        a.flatMap { c -> b.map { t -> ImageToTextSolution(c, t) } }
      }
a
ah, sorry, the problem is
ImageToTextResponse
, not
ImageToTextSolution
c
I'll have to read the docs more. I don't understand right now.
I guess I still don't understand. Extensions are life cycle hooks, like to run code before or after tests. Why would constructing an object within a test need an extension?