Skip to content

Commit

Permalink
airspec (feature): Add initDesign and initLocalDesign for configuring…
Browse files Browse the repository at this point in the history
… test designs (#3086)

Closes #3085
  • Loading branch information
xerial authored Aug 3, 2023
1 parent b37e621 commit ed1faeb
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 17 deletions.
26 changes: 21 additions & 5 deletions airspec/src/main/scala/wvlet/airspec/AirSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ private[airspec] trait AirSpecSpi extends AirSpecSpiCompat {
}
}
}
private[airspec] var _designInitializer: Design => Design = identity
private[airspec] var _localDesignInitializer: Design => Design = identity

/**
* Register a new test. If a custom Design is provided, it will be used to populate the arguments of the test body
Expand Down Expand Up @@ -86,12 +88,26 @@ private[airspec] trait AirSpecSpi extends AirSpecSpiCompat {

/**
* Provide a test-case local design in the spec.
*
* Note that if you override a global design in this method, test cases will create test-case local instances
* (singletons)
*/
protected def localDesign: Design = Design.empty

/**
* Initialize the design before starting tests in this spec
* @param design
* update the function
*/
protected def initDesign(design: Design => Design): Unit = {
_designInitializer = design
}

/**
* Initialize the test-case local design before running each test
* @param design
*/
protected def initLocalDesign(design: Design => Design): Unit = {
_localDesignInitializer = design
}

protected def beforeAll: Unit = {}
protected def before: Unit = {}
protected def after: Unit = {}
Expand Down Expand Up @@ -120,8 +136,8 @@ private[airspec] object AirSpecSpi {
* This wrapper is used for accessing protected methods in AirSpec
*/
private[airspec] implicit class AirSpecAccess(val airSpec: AirSpecSpi) extends AnyVal {
def callDesign: Design = airSpec.design
def callLocalDesign: Design = airSpec.localDesign
def callDesign: Design = airSpec._designInitializer(airSpec.design)
def callLocalDesign: Design = airSpec._localDesignInitializer(airSpec.localDesign)
def callBeforeAll: Unit = airSpec.beforeAll
def callBefore: Unit = airSpec.before
def callAfter: Unit = airSpec.after
Expand Down
21 changes: 21 additions & 0 deletions airspec/src/test/scala/examples/CustomDesignTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package examples

import wvlet.airspec.AirSpec

class CustomDesignTest extends AirSpec {

initDesign {
_.bind[String]
.toInstance("custom string")
.bind[Int].toInstance(10)
}

initLocalDesign {
_.bind[Int].toInstance(1000)
}

test("customDesign") { (s: String, i: Int) =>
s shouldBe "custom string"
i shouldBe 1000
}
}
22 changes: 10 additions & 12 deletions docs/airspec.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,19 +334,19 @@ AirSpec manages two types of sessions: _global_ and _local_:
- It is possible to override the local design by using `test(..., design = ...)` function.

To configure the design of objects that will be created in each session,
override `protected def design:Design` or `protected def localDesign: Design` methods in AirSpec.
configure design by calling `initDesign(d: Design => Design)` (global design) or `initLocalDesign(d: Design => Design)`. It is also possible to customie design by overriding `protected def design:Design` or `protected def localDesign: Design` methods in AirSpec. initDesign and initLocalDesign methods are shortcuts for configuring design and localDesign methods.

### Session LifeCycle

AirSpec manages global/local sessions in this order:

- Create a new instance of AirSpec
- Run `beforeAll`
- Call `design` to prepare a new global design
- Call `initDesign(design)` to prepare a new global design
- Start a new global session
- for each test method _m_:
- Call `before`
- Call `localDesign` to prepare a new local design
- Call `initLocalDesign(localDesign)` to prepare a new local design
- Start a new local session
- Call the test method _m_ by building method arguments using the local session (and the global session). See also [Airframe DI: Child Sessions](https://wvlet.org/airframe/docs/airframe.html#child-sessions) to learn more about the dependency resolution order.
- Shutdown the local session. All data in the local session will be discarded
Expand All @@ -360,14 +360,13 @@ AirSpec manages global/local sessions in this order:
This is an example to utilize a global session to share the same service instance between test methods:
```scala
import wvlet.airspec._
import wvlet.airframe._

case class ServiceConfig(port:Int)
class Service(val config:ServiceConfig)

class ServiceSpec extends AirSpec {
override protected def design: Design = {
newDesign
initDesign { design =>
design
.bind[ServiceConfig].toInstance(ServiceConfig(port=8080))
.bind[Service].toSingleton
.onStart{x => info(s"Starting a server at ${x.config.port}")}
Expand Down Expand Up @@ -401,8 +400,8 @@ It is also possible to reuse the same injected instance by nesting `test` method

```scala
class ServiceSpec extends AirSpec {
override protected def design: Design = ...
test("server test") { service:Service =>
initDesign { _.bind[Service].toInstance(...) }
test("server test") { (service:Service) =>
test("test 1") {
info(s"server id: ${service.hashCode}")
}
Expand All @@ -425,9 +424,8 @@ import wvlet.airframe._

class OverrideTest extends AirSpec {

override protected def design: Design = {
newDesign
.bind[String].toInstance("hello")
initDesign {
_.bind[String].toInstance("hello")
}

// Pass Session to override the design
Expand Down Expand Up @@ -502,7 +500,7 @@ object AppTestModule {
import wvlet.airspec._
class AppTest extends AirSpec {
// Use the testing design
protected override def design = AppTestModule.serviceDesignForTests
initDesign(_ + AppTestModule.serviceDesignForTests)

// Inject a Service object initialized with a test configuration
test("start up test") { (service:Service) =>
Expand Down

0 comments on commit ed1faeb

Please sign in to comment.