/
FixtureTestSuite.scala
288 lines (276 loc) · 13.4 KB
/
FixtureTestSuite.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/*
* Copyright 2001-2013 Artima, Inc.
*
* 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.
*/
package org.scalatest
import org.scalatest._
import OutcomeOf.outcomeOf
import org.scalactic._
/**
* The base trait for ScalaTest's synchronous testing styles that accept a fixture
* object passed into tests. This trait defines a
* <code>withFixture</code> lifecycle method that takes as its parameter a test function
* that accepts a fixture object and returns an <a href="Outcome.html"><code>Outcome</code></a>.
*
* <p>
* The abstract <code>withFixture</code> method add by this trait has the
* following signature:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">def</span> withFixture(test: <span class="stType">OneArgTest</span>): <span class="stType">Outcome</span>
* </pre>
*
* The <code>apply</code> method of test function interface,
* <code>OneArgTest</code>, also returns <code>Outcome</code>:
* </p>
*
* <pre class="stHighlighted">
* <span class="stLineComment">// In trait OneArgTest:</span>
* <span class="stReserved">def</span> apply(fixture: <span class="stType">FixtureParam</span>): <span class="stType">Outcome</span>
* </pre>
*
* <p>
* Because the result of a test is an <code>Outcome</code>, when the test function returns, the test body must have determined an outcome already. It
* will already be one of <a href="Succeeded$.html"><code>Succeeded</code></a>, <a href="Failed.html"><code>Failed</code></a>, <a href="Canceled.html"><code>Canceled</code></a>, or <a href="Pending$.html"></code>Pending</code></a>. This is
* also true when <code>withFixture(OneArgTest)</code> returns: because the result type of <code>withFixture(OneArgTest)</code> is <code>Outcome</code>,
* the test has by definition already finished execution.
* </p>
*
* <p>
* The recommended way to ensure cleanup is performed after a test body finishes execution is
* to use a <code>try</code>-<code>finally</code> clause.
* Using <code>try</code>-<code>finally</code> will ensure that cleanup will occur whether
* the test function completes abruptly by throwing a suite-aborting exception, or returns
* normally yielding an <code>Outcome</code>. Note that the only situation in which a test function
* will complete abruptly with an exception is if the test body throws a suite-aborting exception.
* Any other exception will be caught and reported as either a <code>Failed</code>, <code>Canceled</code>,
* or <code>Pending</code>.
* </p>
*
* <p>
* To enable the stacking of traits that define <code>withFixture(NoArgTest)</code>, it is a good idea to let
* <code>withFixture(NoArgTest)</code> invoke the test function instead of invoking the test
* function directly. To do so, you'll need to convert the <code>OneArgTest</code> to a <code>NoArgTest</code>. You can do that by passing
* the fixture object to the <code>toNoArgTest</code> method of <code>OneArgTest</code>. In other words, instead of
* writing “<code>test(theFixture)</code>”, you'd delegate responsibility for
* invoking the test function to the <code>withFixture(NoArgTest)</code> method of the same instance by writing:
* </p>
*
* <pre>
* withFixture(test.toNoArgTest(theFixture))
* </pre>
*
* <p>
* The <code>withFixture</code> method is designed to be stacked, and to enable this, you should always call the <code>super</code> implementation
* of <code>withFixture</code>, and let it invoke the test function rather than invoking the test function directly. In other words, instead of writing
* “<code>test(...)</code>”, you should write “<code>super.withFixture(test)</code>”. Thus, the recommended
* structure of a <code>withFixture</code> implementation that performs cleanup looks like this:
* </p>
*
* <pre class="stHighlighted">
* <span class="stLineComment">// Your implementation</span>
* <span class="stReserved">type</span> <span class="stType">FixtureParam</span> = <span class="stType">String</span>
* <br/><span class="stReserved">override</span> <span class="stReserved">def</span> withFixture(test: <span class="stType">OneArgTest</span>) = {
* <span class="stLineComment">// Perform setup here</span>
* <span class="stReserved">val</span> theFixture = <span class="stQuotedString">"hello"</span>
* <span class="stReserved">try</span> {
* withFixture(test.toNoArgTest(theFixture)) <span class="stLineComment">// Invoke the test function</span>
* } <span class="stReserved">finally</span> {
* <span class="stLineComment">// Perform cleanup here</span>
* }
* }
* </pre>
*
* <p>
* If you have no cleanup to perform, you can write <code>withFixture</code> like this instead:
* </p>
*
* <pre class="stHighlighted">
* <span class="stLineComment">// Your implementation</span>
* <span class="stReserved">type</span> <span class="stType">FixtureParam</span> = <span class="stType">String</span>
* <br/><span class="stReserved">override</span> <span class="stReserved">def</span> withFixture(test: <span class="stType">NoArgTest</span>) = {
* <span class="stLineComment">// Perform setup here</span>
* <span class="stReserved">val</span> theFixture = <span class="stQuotedString">"hello"</span>
* withFixture(test.toNoArgTest(theFixture)) <span class="stLineComment">// Invoke the test function</span>
* }
* </pre>
*
* <p>
* If you want to perform an action only for certain outcomes, you can use
* a pattern match.
* For example, if you want to perform an action if a test fails, you'd
* match on <code>Failed</code>, like this:
* </p>
*
* <pre class="stHighlighted">
* <span class="stLineComment">// Your implementation</span>
* <span class="stReserved">type</span> <span class="stType">FixtureParam</span> = <span class="stType">String</span>
* <br/><span class="stReserved">override</span> <span class="stReserved">def</span> withFixture(test: <span class="stType">NoArgTest</span>) = {
* <br/> <span class="stLineComment">// Perform setup here</span>
* <span class="stReserved">val</span> theFixture = <span class="stQuotedString">"hello"</span>
* <br/> <span class="stReserved">val</span> outcome = withFixture(test.toNoArgTest(theFixture)) <span class="stLineComment">// Invoke the test function</span>
* <br/> outcome <span class="stReserved">match</span> {
* <span class="stReserved">case</span> failed: <span class="stType">Failed</span> =>
* <span class="stLineComment">// perform action that you want to occur</span>
* <span class="stLineComment">// only if a test fails here</span>
* failed
* <span class="stReserved">case</span> other => other
* }
* }
* </pre>
*
* <p>
* If you want to change the outcome in some way in <code>withFixture</code>, you can also
* use a pattern match.
* For example, if a particular exception intermittently causes a test to fail, and can
* transform those failures into cancelations, like this:
* </p>
*
* <pre class="stHighlighted">
* <span class="stLineComment">// Your implementation</span>
* <span class="stReserved">type</span> <span class="stType">FixtureParam</span> = <span class="stType">String</span>
* <br/><span class="stReserved">override</span> <span class="stReserved">def</span> withFixture(test: <span class="stType">NoArgTest</span>) = {
* <br/> <span class="stReserved">val</span> theFixture = <span class="stQuotedString">"hello"</span>
* <br/> withFixture(test.toNoArgTest(theFixture)) <span class="stReserved">match</span> {
* <span class="stReserved">case</span> <span class="stType">Failed</span>(ex: <span class="stType">ParticularException</span>) =>
* <span class="stType">Canceled</span>(<span class="stQuotedString">"Muting flicker"</span>, ex)
* <span class="stReserved">case</span> other => other
* }
* }
* </pre>
*/
trait FixtureTestSuite extends org.scalatest.FixtureSuite with org.scalatest.TestSuite { thisTestSuite =>
/**
* A test function taking a fixture parameter and returning an <code>Outcome</code>.
*
* <p>
* For more detail and examples, see the
* <a href="FlatSpec.html">documentation for trait <code>fixture.FlatSpec</code></a>.
* </p>
*/
protected trait OneArgTest extends (FixtureParam => Outcome) with TestData { thisOneArgTest =>
/**
* Runs the test, using the passed <code>FixtureParam</code>.
*
* @param fixture the <code>FixtureParam</code>
* @return an instance of <code>Outcome</code>
*/
def apply(fixture: FixtureParam): Outcome
/**
* Convert this <code>OneArgTest</code> to a <code>NoArgTest</code> whose
* <code>name</code> and <code>configMap</code> methods return the same values
* as this <code>OneArgTest</code>, and whose <code>apply</code> method invokes
* this <code>OneArgTest</code>'s apply method,
* passing in the given <code>fixture</code>.
*
* <p>
* This method makes it easier to invoke the <code>withFixture</code> method
* that takes a <code>NoArgTest</code>. For example, if a <code>FixtureSuite</code>
* mixes in <code>SeveredStackTraces</code>, it will inherit an implementation
* of <code>withFixture(NoArgTest)</code> provided by
* <code>SeveredStackTraces</code> that implements the stack trace severing
* behavior. If the <code>FixtureSuite</code> does not delegate to that
* <code>withFixture(NoArgTest)</code> method, the stack trace severing behavior
* will not happen. Here's how that might look in a <code>FixtureSuite</code>
* whose <code>FixtureParam</code> is <code>StringBuilder</code>:
* </p>
*
* <pre class="stHighlighted">
* <span class="stReserved">def</span> withFixture(test: <span class="stType">OneArgTest</span>) = {
* withFixture(test.toNoArgTest(<span class="stReserved">new</span> <span class="stType">StringBuilder</span>))
* }
* </pre>
*
* <p>
* Invoking this method has no side effect. It just returns a <code>NoArgTest</code> whose
* <code>apply</code> method invokes <code>apply</code> on this <code>OneArgTest</code>, passing
* in the <code>FixtureParam</code> passed to <code>toNoArgTest</code>.
* </p>
*
* @param fixture the <code>FixtureParam</code>
* @return an new instance of <code>NoArgTest</code>
*/
def toNoArgTest(fixture: FixtureParam) =
new NoArgTest {
val name = thisOneArgTest.name
val configMap = thisOneArgTest.configMap
def apply(): Outcome = { thisOneArgTest(fixture) }
val scopes = thisOneArgTest.scopes
val text = thisOneArgTest.text
val tags = thisOneArgTest.tags
val pos = thisOneArgTest.pos
}
}
/**
* Companion object for <code>OneArgTest</code> that provides factory method to create new <code>OneArgTest</code>
* instance by passing in a <code>OneArgTest</code> and a <code>FixtureParam</code> => <code>Outcome</code> function.
*/
object OneArgTest {
/**
* Create new <code>OneArgTest</code> instance.
*
* @param test a <code>OneArgTest</code>
* @param f a <code>FixtureParam</code> => <code>Outcome</code> function
* @return a new instance of <code>OneArgTest</code>, which will call the passed <code>f</code> function in its <code>apply</code> method
*/
def apply(test: OneArgTest)(f: FixtureParam => Outcome): OneArgTest = {
new OneArgTest {
def apply(fixture: FixtureParam): Outcome = { f(fixture) }
val text: String = test.text
val configMap: ConfigMap = test.configMap
val scopes: collection.immutable.IndexedSeq[String] = test.scopes
val name: String = test.name
val tags: Set[String] = test.tags
val pos: Option[source.Position] = test.pos
}
}
}
/**
* Run the passed test function with a fixture created by this method.
*
* <p>
* This method should create the fixture object needed by the tests of the
* current suite, invoke the test function (passing in the fixture object),
* and if needed, perform any clean up needed after the test completes.
* For more detail and examples, see the <a href="Suite.html">main documentation for this trait</a>.
* </p>
*
* @param test the <code>OneArgTest</code> to invoke, passing in a fixture
* @return an instance of <code>Outcome</code>
*/
protected def withFixture(test: OneArgTest): Outcome
private[scalatest] class TestFunAndConfigMap(val name: String, test: FixtureParam => Any, val configMap: ConfigMap)
extends OneArgTest {
def apply(fixture: FixtureParam): Outcome = {
outcomeOf { test(fixture) }
}
private val testData = testDataFor(name, configMap)
val scopes = testData.scopes
val text = testData.text
val tags = testData.tags
val pos = testData.pos
}
private[scalatest] class FixturelessTestFunAndConfigMap(override val name: String, test: () => Any, override val configMap: ConfigMap)
extends NoArgTest {
def apply(): Outcome = {
outcomeOf { test() }
}
private val testData = testDataFor(name, configMap)
val scopes = testData.scopes
val text = testData.text
val tags = testData.tags
val pos = testData.pos
}
}