Testing Typescript Projects

This page will discuss common topics around working with test suites using the Mill build tool

Defining Unit Test Suites

build.mill (download, browse)
package build

import mill._, javascriptlib._

object bar extends TypeScriptModule {
  object test extends TypeScriptTests with TestModule.Jest
}

object baz extends TypeScriptModule {
  object test extends TypeScriptTests with TestModule.Vitest
}

object foo extends TypeScriptModule {
  object test extends TypeScriptTests with TestModule.Mocha
}

object qux extends TypeScriptModule {
  object test extends TypeScriptTests with TestModule.Jasmine
}

This build defines 4 modules bar, baz, foo & qux with test suites configured to use Jest, Vitest, Mocha & Jasmine respectively.

Mill will auto-magically generate test configurations for each respective test suite. Custom test configurations can be used by simply including a matching test suite config file in your project root, same directory as your build.mill file.

You can view the generated config file by looking in the compile destination for your modules test. For example, the module bar will have its jest.config.ts file live in out/bar/test/compile.dest/.

It is important to note, that for most use cases you will never need to define a custom test configurtion file.

For custom configurations:

Jest suite expects a jest.config.ts file.

Jasmine suite expects a jasmine.json file.

Mocha suite expects a test-runner.js file.

Vitest suite expects a vitest.config.ts file.

> mill foo.test
...
...4 passing...
...

> mill bar.test
...Calculator
...
Test Suites:...1 passed, 1 total...
Tests:...4 passed, 4 total...
...

> mill baz.test
.../calculator.test.ts...
...Test Files  1 passed...
...Tests  4 passed...
...

> mill qux.test
...
4 specs, 0 failures

Test Dependencies

build.mill (download, browse)
package build

import mill._, javascriptlib._

object bar extends TypeScriptModule {
  def npmDeps = Seq("immutable@4.3.7")
  object test extends TypeScriptTests with TestModule.Jest
}

object foo extends TypeScriptModule {
  def moduleDeps = Seq(bar)
  object test extends TypeScriptTests with TestModule.Jest
}

Documentation for mill.example.javascriptlib In this example, foo depend on bar, but we also make foo.test depend on bar.test.

That lets foo.test make use of the default function exported from bar/test/utils/bar.tests.utils.ts, allowing us to re-use this test helper throughout multiple modules' test suites.

> mill foo.test
PASS .../foo.test.ts
...
Test Suites:...1 passed, 1 total...
Tests:...3 passed, 3 total...
...

> mill bar.test
PASS .../bar.test.ts
...
Test Suites:...1 passed, 1 total...
Tests:...1 passed, 1 total...
...

Integration Suite with Cypress

build.mill (download, browse)
package build

import mill._, javascriptlib._

object client extends ReactScriptsModule

object server extends TypeScriptModule {

  def npmDeps =
    Seq("@types/cors@^2.8.17", "@types/express@^5.0.0", "cors@^2.8.5", "express@^4.21.1")

  /** Bundle client as resource */
  def resources = Task {
    os.copy(client.bundle().path, Task.dest / "build")
    super.resources() ++ Seq(PathRef(Task.dest))
  }

  override def forkEnv = super.forkEnv() + ("PORT" -> "4000")

  object test extends TypeScriptTests with TestModule.Cypress {
    def service = server
    def port = "4000"
  }
}

Documentation for mill.example.javascriptlib In this example we demonstrate integration testing using cypress mill server.test will start the service on the speicifed port, run tests with configurations defined in cypress.config.ts and kill the service once completed

> mill server.test
...
...Server listening on port 4000
... app.cy.ts...
... All specs passed!...
...

Integration Suite with PlayWright

build.mill (download, browse)
package build

import mill._, javascriptlib._

object client extends ReactScriptsModule

object server extends TypeScriptModule {

  def npmDeps =
    Seq("@types/cors@^2.8.17", "@types/express@^5.0.0", "cors@^2.8.5", "express@^4.21.1")

  /** Bundle client as resource */
  def resources = Task {
    os.copy(client.bundle().path, Task.dest / "build")
    super.resources() ++ Seq(PathRef(Task.dest))
  }

  def forkEnv = super.forkEnv() + ("PORT" -> "3000")

  object test extends TypeScriptTests with TestModule.PlayWright {
    def service = server
    def port = "6000"
  }
}

Documentation for mill.example.javascriptlib In this example we demonstrate integration testing using playwright mill server.test will start the service on the speicifed port, run tests with configurations defined in playwright.config.ts and kill the service once completed

> mill server.test
...
...Server listening on port 6000
...
...1 passed...
...