0

I share data between ScalaTest suites using a common singleton Object. This works fine when running on JVM, but to my surprise it fails on Scala.js. It seems on Scala.js a fresh instance of the program is used for each suite and the singletons are not shared.

Consider following code:

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

object SharedTestData {
  var useCount = 0

  def useMe(): Int = {
    useCount += 1
    useCount
  }
}

class ATest extends AnyFlatSpec with Matchers {
  "This" should "pass" in {
    val used = SharedTestData.useMe()
    info(s"Used $used times")
  }
}

class BTest extends AnyFlatSpec with Matchers {
  "This" should "pass as well" in {
    val used = SharedTestData.useMe()
    info(s"Used $used times")
  }
}

The output from sbt test with project configured as Scala.js is:

[info] BTest:
[info] This
[info] - should pass as well
[info]   + Used 1 times
[info] ATest:
[info] This
[info] - should pass
[info]   + Used 1 times
[info] Run completed in 422 milliseconds.
[info] Total number of tests run: 2

Is there any technique which would allow me to share data between test suites on Scala.js? Some of the data take several minutes to compute and it is annoying computing them repeatedly.

Suma
  • 33,181
  • 16
  • 123
  • 191

2 Answers2

1

By default, for speed, build tools such as sbt parallelize the execution of tests. In the Scala.js world, this is implemented by spawning several JavaScript VMs; one for each thread. Since separate JS VMs do not share memory, every top-level object in the code is instantiated once for each JS VM.

The initialization of those objects should execute in parallel, so in most cases you shouldn't notice an increase in initialization time due to these separate instances. However, multi-tasking is complicated, and so it is possible in some cases for things to get slower.

You can enforce a single initialization by disabling parallel test execution in your build tool. In sbt, you can do it with the following setting:

.settings(
  ...
  Test / parallelExecution := false,
)

That will ensure that only once JS VM is used. But it will also mean that all the tests in your test suite will run sequentially, without taking advantage of your multiple cores.

sjrd
  • 21,805
  • 2
  • 61
  • 91
0

You can group multiple suites using Suites - see also Group ScalaTest tests and run in order. Any such group is run as a single suite, therefore using a single VM.

There is one more thing to do, though, as @DoNotDiscover does not seem to work on Scala.js. You need to prevent SBT running the individual suites, which you can do using Test / testOptions += Tests.Filter(s => !s.endsWith("SubTest")) in SBT and naming your suites accordingly.

Here is the source:

@DoNotDiscover
class ASubTest extends AnyFlatSpec with Matchers {
  "This" should "pass" in {
    val used = SharedTestData.useMe()
    info(s"Used $used times")
  }
}

@DoNotDiscover
class BSubTest extends AnyFlatSpec with Matchers {
  "This" should "pass as well" in {
    val used = SharedTestData.useMe()
    info(s"Used $used times")
  }
}

class MySuite extends Suites(new ASubTest, new BSubTest)

And the output is:

[info] ASubTest:
[info] This
[info] - should pass
[info]   + Used 1 times
[info] BSubTest:
[info] This
[info] - should pass as well
[info]   + Used 2 times
Suma
  • 33,181
  • 16
  • 123
  • 191