Skip to content

Instantly share code, notes, and snippets.

@Topher-the-Geek
Created April 13, 2012 17:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Topher-the-Geek/2378392 to your computer and use it in GitHub Desktop.
Save Topher-the-Geek/2378392 to your computer and use it in GitHub Desktop.
Monadic Test Fixtures for Scalatest
/*
* 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)
}
/*
* 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)
}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment