Skip to content

Instantly share code, notes, and snippets.

@Topher-the-Geek
Created April 13, 2012 17:06
Show Gist options
  • Select an option

  • Save Topher-the-Geek/2378392 to your computer and use it in GitHub Desktop.

Select an option

Save Topher-the-Geek/2378392 to your computer and use it in GitHub Desktop.

Revisions

  1. @invalid-email-address Anonymous created this gist Apr 13, 2012.
    109 changes: 109 additions & 0 deletions FixturesSpec.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,109 @@
    /*
    * Copyright 2012, Christopher A. Olson
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    import org.junit.runner.RunWith
    import org.scalatest.FlatSpec
    import org.scalatest.junit.JUnitRunner

    @RunWith (classOf [JUnitRunner])
    class FixtureSpec extends FlatSpec {

    def withInt (i: Int) = WithFixture {
    (test: Int => Any) => test (i)
    }

    "A simple fixture" should "apply" in {
    var ran = false
    withInt (1) { i =>
    expect (1) (i)
    ran = true
    }
    expect (true) (ran)
    }

    it should "do foreach" in {
    var ran = false
    for (i <- withInt (1)) {
    expect (1) (i)
    ran = true
    }
    expect (true) (ran)
    }

    it should "do filter, applying matching arguments" in {
    var ran = false
    for { i <- withInt (1); if i == 1 } {
    expect (1) (i)
    ran = true
    }
    expect (true) (ran)
    }

    it should "do filter, skipping non-matching arguments" in {
    var ran = false
    intercept [Exception] {
    for { i <- withInt (1); if i == 2 } {
    expect (2) (i)
    ran = true
    }}
    expect (false) (ran)
    }

    it should "do map" in {
    var ran = false
    for { i <- withInt (1); val j = i + 1 } {
    expect (1) (i)
    expect (2) (j)
    ran = true
    }
    expect (true) (ran)
    }

    def withInts (i: Int, j: Int) = WithFixture.tuple {
    (test: (Int, Int) => Any) => test (i, j)
    }

    "A tupled fixture" should "apply" in {
    var ran = false
    withInts (1, 2) { v: (Int, Int) =>
    expect (1) (v._1)
    expect (2) (v._2)
    ran = true
    }
    expect (true) (ran)
    }

    // The translation of the for statement includes filter.
    it should "do filter and foreach" in {
    var ran = false
    for ((i, j) <- withInts (1, 2)) {
    expect (1) (i)
    expect (2) (j)
    ran = true
    }
    expect (true) (ran)
    }

    it should "do map" in {
    var ran = false
    for { (i, j) <- withInts (1, 2); val k = i + j } {
    expect (1) (i)
    expect (2) (j)
    expect (3) (k)
    ran = true
    }
    expect (true) (ran)
    }}
    141 changes: 141 additions & 0 deletions fixtures.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,141 @@
    /*
    * Copyright 2012, Christopher A. Olson
    *
    * Licensed under the Apache License, Version 2.0 (the "License");
    * you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at
    *
    * http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing, software
    * distributed under the License is distributed on an "AS IS" BASIS,
    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */

    /** If a test requires some objects to run, you may set them up and tear them down in a fixture
    * as follows:
    *
    * '''
    * def withFile (path: String) = new WithFixture {
    * def apply (test: FileInputStream => B): B = {
    * val file = new FileInputStream (path)
    * try {
    * test (file)
    * } finally {
    * file.close()
    * }}}
    * '''
    *
    * See `WithFixture.apply` for another pattern to create fixtures.
    *
    * Fixtures may easily be composed with Scala for statements:
    *
    * '''
    * for {
    * (httpPort, httpsPort) <- withServer()
    * driver <- withChromeDriver()
    * (empId, depId) <- loadDbWithEmpAndDep()
    * } {
    * ...do the test...
    * }
    * '''
    *
    * A fixture returns the test's final result. Using `yield` in a for statement does not have the
    * desired effect: instead of the test's value, you obtain a `WithFixture` which must be applied to
    * the identity function. However, the a fixture that may return a value can be used in a couple
    * different ways, depending on the needs of a particular test:
    *
    * '''
    * // Just one simple test with a PageObject.
    * for {
    * driver <- withFirefoxDriver()
    * editor <- withMyFormPageObject (driver)
    * } {
    * ...do the test, no worries about the final value...
    * }
    *
    * // A more complex test with data passed from PageObject to PageObject.
    * for (driver <- withOperaDriver()) {
    * val gadgetId = withMyFormPageObject (driver) { form =>
    * ...fill in the form, get the some id from it...
    * }
    *
    * withMyNavigatorPageObject (driver) { nav =>
    * .. use the gadgetId found above to find some link in the navigation menu...
    * }}
    * '''
    */
    abstract class WithFixture [A, B] extends ((A => B) => B) {

    // Multiple argument fixtures require `filter`.
    def filter (p: A => Boolean): WithFixture [A, B] = new FilterFixture (p, this)

    def foreach (t: A => B): Unit = apply (t)

    // A val statement in the for declaration requires `map`.
    def map [C] (f: A => C): WithFixture [C, B] = new MapFixture (f, this)

    def withFilter (p: A => Boolean): WithFixture [A, B] = new FilterFixture (p, this)
    }

    private class ApplyFixture [A, B] (fixture: (A => B) => B)
    extends WithFixture [A, B] {

    def apply (test: A => B): B = fixture (test)
    }

    private class ApplyFixture2 [A1, A2, B] (fixture: ((A1, A2) => B) => B)
    extends WithFixture [(A1, A2), B] {

    def apply (test: ((A1, A2)) => B): B =
    fixture ((a: A1, b: A2) => test (a, b))
    }

    private class ApplyFixture3 [A1, A2, A3, B] (fixture: ((A1, A2, A3) => B) => B)
    extends WithFixture [(A1, A2, A3), B] {

    def apply (test: ((A1, A2, A3)) => B): B =
    fixture ((a: A1, b: A2, c: A3) => test (a, b, c))
    }

    private class FilterFixture [A, B] (p: A => Boolean, fixture: (A => B) => B)
    extends WithFixture [A, B] {

    def apply (test: A => B): B =
    // TODO: What to do when the predicate fails?
    fixture ((x: A) => if (p (x)) test (x) else throw new Exception)
    }

    private class MapFixture [A, C, B] (f: A => C, fixture: (A => B) => B)
    extends WithFixture [C, B] {

    def apply (test: C => B): B = fixture ((x: A) => test (f (x)))
    }

    object WithFixture {

    /** If a test requires some objects to run, you may set them up and tear them down in a fixture
    * as follows:
    *
    * '''
    * def withFile (path: String) =
    * WithFixture { test: (FileInputStream => B) =>
    * val file = new FileInputStream (path)
    * try {
    * test (file)
    * } finally {
    * file.close()
    * }}
    * '''
    */
    def apply [A, B] (fixture: (A => B) => B): WithFixture [A, B] =
    new ApplyFixture (fixture)

    def tuple [A1, A2, B] (fixture: ((A1, A2) => B) => B): WithFixture [(A1, A2), B] =
    new ApplyFixture2 (fixture)

    def tuple3 [A1, A2, A3, B] (fixture: ((A1, A2, A3) => B) => B): WithFixture [(A1, A2, A3), B] =
    new ApplyFixture3 (fixture)
    }