Created
April 13, 2012 17:06
-
-
Save Topher-the-Geek/2378392 to your computer and use it in GitHub Desktop.
Monadic Test Fixtures for Scalatest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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