CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scala-js--scalajs-junit-test-runtime

JUnit test runtime for Scala.js - provides the runtime infrastructure for running JUnit tests compiled with Scala.js

Pending
Overview
Eval results
Files

test-lifecycle.mddocs/

Test Lifecycle

JUnit provides annotations to control test execution flow, allowing setup and teardown operations at both method and class levels. These annotations ensure tests run in isolation with proper initialization and cleanup.

Core Test Annotations

@Test Annotation

class Test(
  expected: Class[_ <: Throwable] = classOf[Test.None], 
  timeout: Long = 0L
) extends StaticAnnotation

object Test {
  final class None private () extends Throwable
}

Basic Usage:

class UserServiceTest {
  @Test
  def shouldCreateNewUser(): Unit = {
    val user = userService.createUser("Alice", "alice@example.com")
    assertNotNull(user)
    assertEquals("Alice", user.getName())
  }
}

Expected Exception Testing:

@Test(expected = classOf[IllegalArgumentException])
def shouldRejectInvalidEmail(): Unit = {
  userService.createUser("Bob", "invalid-email")
}

Timeout Testing:

@Test(timeout = 1000L) // 1 second timeout
def shouldCompleteQuickly(): Unit = {
  val result = quickOperation()
  assertNotNull(result)
}

Combined Parameters:

@Test(expected = classOf[TimeoutException], timeout = 5000L)
def shouldTimeoutWithException(): Unit = {
  slowOperationThatTimesOut()
}

Method-Level Lifecycle

@Before - Setup Before Each Test

class Before extends StaticAnnotation

Usage:

class DatabaseTest {
  private var database: Database = _
  
  @Before
  def setUp(): Unit = {
    database = new TestDatabase()
    database.connect()
    database.initializeSchema()
  }
  
  @Test
  def shouldInsertUser(): Unit = {
    val user = User("Alice", "alice@example.com")
    database.insert(user)
    assertEquals(1, database.count())
  }
  
  @Test  
  def shouldUpdateUser(): Unit = {
    val user = User("Bob", "bob@example.com")
    database.insert(user)
    database.update(user.copy(email = "bob.smith@example.com"))
    assertEquals("bob.smith@example.com", database.findById(user.id).email)
  }
}

@After - Cleanup After Each Test

class After extends StaticAnnotation

Usage:

class FileSystemTest {
  private var tempDir: Path = _
  
  @Before
  def createTempDirectory(): Unit = {
    tempDir = Files.createTempDirectory("test")
  }
  
  @After
  def cleanupTempDirectory(): Unit = {
    if (tempDir != null) {
      Files.walk(tempDir)
        .sorted(java.util.Comparator.reverseOrder())
        .forEach(Files.delete)
    }
  }
  
  @Test
  def shouldCreateFile(): Unit = {
    val file = tempDir.resolve("test.txt")
    Files.write(file, "content".getBytes)
    assertTrue(Files.exists(file))
  }
}

Class-Level Lifecycle

@BeforeClass - One-Time Setup

class BeforeClass extends StaticAnnotation

Usage:

class IntegrationTest {
  companion object {
    private var server: TestServer = _
    
    @BeforeClass
    def startServer(): Unit = {
      server = new TestServer(8080)
      server.start()
      // Wait for server to be ready
      Thread.sleep(1000)
    }
    
    @AfterClass
    def stopServer(): Unit = {
      if (server != null) {
        server.stop()
      }
    }
  }
  
  @Test
  def shouldRespondToHealthCheck(): Unit = {
    val response = httpClient.get("http://localhost:8080/health")
    assertEquals(200, response.getStatusCode())
  }
}

@AfterClass - One-Time Cleanup

class AfterClass extends StaticAnnotation

Methods annotated with @AfterClass are executed once after all test methods in the class have run.

Test Exclusion

@Ignore - Skip Tests

class Ignore(value: String = "") extends StaticAnnotation

Usage:

class FeatureTest {
  @Test
  def shouldWork(): Unit = {
    // This test runs normally
    assertTrue(feature.isEnabled())
  }
  
  @Ignore("Feature not implemented yet")
  @Test
  def shouldHandleAdvancedCase(): Unit = {
    // This test is skipped
    feature.advancedOperation()
  }
  
  @Ignore // No reason provided
  @Test
  def temporarilyDisabled(): Unit = {
    // This test is also skipped
    flakeyOperation()
  }
}

Ignoring Entire Test Classes:

@Ignore("Integration tests disabled in CI")
class SlowIntegrationTest {
  @Test
  def shouldConnectToExternalService(): Unit = {
    // All tests in this class are skipped
  }
}

Rule Support Annotations

@Rule - Method-Level Rules

trait Rule extends Annotation

Usage:

class RuleTest {
  @Rule
  val tempFolder: TemporaryFolder = new TemporaryFolder()
  
  @Rule
  val timeout: Timeout = new Timeout(10000) // 10 second timeout for all tests
  
  @Test
  def shouldUseTemporaryFolder(): Unit = {
    val file = tempFolder.newFile("test.txt")
    Files.write(file.toPath, "content".getBytes)
    assertTrue(file.exists())
  }
}

@ClassRule - Class-Level Rules

trait ClassRule extends Annotation

Usage:

class ClassRuleTest {
  companion object {
    @ClassRule
    val externalResource: ExternalResource = new ExternalResource() {
      override def before(): Unit = {
        // Setup expensive resource once for all tests
        println("Setting up external resource")
      }
      
      override def after(): Unit = {
        // Cleanup expensive resource after all tests
        println("Cleaning up external resource")
      }
    }
  }
  
  @Test
  def shouldUseExternalResource(): Unit = {
    // Use the external resource
    assertTrue(externalResource.isAvailable())
  }
}

Execution Order

JUnit executes lifecycle methods in the following order:

  1. @BeforeClass methods (once per class)
  2. For each test method:
    • @Before methods (before each test)
    • @Test method (the actual test)
    • @After methods (after each test, even if test fails)
  3. @AfterClass methods (once per class, even if tests fail)

Exception Handling:

  • If @Before fails, the test method is skipped but @After still runs
  • If @Test fails, @After still runs
  • If @After fails, it doesn't affect other tests
  • @AfterClass always runs even if @BeforeClass or tests fail

Best Practices

  1. Resource Management: Always pair setup with cleanup (@Before with @After, @BeforeClass with @AfterClass)

  2. Exception Safety: Use try-finally or similar patterns in @After methods to ensure cleanup

  3. Test Isolation: Each test should be independent - @Before and @After ensure clean state

  4. Expensive Resources: Use @BeforeClass/@AfterClass for expensive setup (database connections, web servers)

  5. Temporary Resources: Use @Rule with TemporaryFolder, TestName, or custom rules for common patterns

Install with Tessl CLI

npx tessl i tessl/maven-org-scala-js--scalajs-junit-test-runtime

docs

array-assertions.md

core-assertions.md

exception-handling.md

hamcrest-matchers.md

index.md

test-assumptions.md

test-lifecycle.md

test-runners.md

tile.json