scalareflectionsbtclassloader

Load dependency class in sbt task


I want to run embedded kafka before all IT test as Tests.Setup from scala object.

So, I have something like

Test / testOptions += Tests.Setup { loader => 
  loader.loadClass("io.github.embeddedkafka.schemaregistry.EmbeddedKafka$").getMethod("start").invoke(null)

The problem is that it can't be found. I've checked loader.getDefinedPackages and there is no classes from io.github. How can I access class from dependency?


Solution

  • If you don't fork (fork := false), when you add a dependency like

    libraryDependencies += "io.github.embeddedkafka" %% "embedded-kafka" % "3.7.0" % Test
    

    to (ordinary) build.sbt and run tests like sbt test, then the code similar to yours in build.sbt should work

    Test / testOptions += Tests.Setup { loader => 
      val cls = loader.loadClass("io.github.embeddedkafka.EmbeddedKafka$")
      val paramCls = loader.loadClass("io.github.embeddedkafka.EmbeddedKafkaConfig")
      val inst = cls.getField("MODULE$").get(null)
      cls.getDeclaredMethod("start", paramCls).invoke(inst)
    }
    

    If you fork (fork := true), then indeed, loader.loadClass... won't work

    Note: When forking, the ClassLoader containing the test classes cannot be provided because it is in another JVM. Only use the () => Unit variants in this case.

    https://www.scala-sbt.org/1.x/docs/Testing.html#Setup+and+Cleanup

    You can add to project/build.sbt

    libraryDependencies += "io.get-coursier" %% "coursier" % "2.1.9"
    

    and create necessary class loader manually writing in ordinary build.sbt

    Test / testOptions += Tests.Setup { loader =>
    
      import coursier._
    
      val files = Fetch()
        .addDependencies(
          dep"io.github.embeddedkafka:embedded-kafka_2.12:3.7.0"
        )
        .run()
    
      val classLoader = new java.net.URLClassLoader(
        files.map(_.toURI.toURL).toArray,
        loader/*getClass.getClassLoader*//*null*/
      )
    
      val cls = classLoader.loadClass("io.github.embeddedkafka.EmbeddedKafka$")
      val paramCls = classLoader.loadClass("io.github.embeddedkafka.EmbeddedKafkaConfig")
      val inst = cls.getField("MODULE$").get(null)
      cls.getDeclaredMethod("start", paramCls).invoke(inst)
    }
    

    https://get-coursier.io/docs/api#fetch-api


    By the way, you can try to make call directly rather than using reflection

    Test / testOptions += Tests.Setup { () => 
      _root_.io.github.embeddedkafka.EmbeddedKafka.start()
    }
    

    In such case

    libraryDependencies += "io.github.embeddedkafka" %% "embedded-kafka" % "3.7.0" exclude ("com.github.luben", "zstd-jni")
    

    should be added to project/build.sbt.