- Notifications
You must be signed in to change notification settings - Fork86
A simple testing framework for Scala
License
com-lihaoyi/utest
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
uTest (pronounced micro-test) is a simple, intuitive testing library for Scala.Its key features are:
- Nicely formatted, colored, easy-to-read command-line test output
- Single uniform syntax for defining tests and grouping them together
- Single uniform syntax for running test suites and individual tests
- Single uniform syntax for Smart Asserts, instead of multipleredundant
must_==
/must beEqual
/should be
opertors - Isolation-by-default for tests in the same suite
- Supports every version of Scala under the sun:Scala.js and Scala-Native, Scala 2.13.0-M2,projects usingSBT orstandalone (e.g. via a
main
method, or inAmmonite Scripts)
Unlike traditional testing libraries for Scala (likeScalatest orSpecs2) uTest aims to be simple enoughyou will never "get lost" in its codebase and functionality, so you can focus onwhat's most important: your tests.
While uTest has many fewer features than other libraries,the features that it does provide are polished and are enough to buildand maintain test suites of any size. uTest is used for countless projects, fromthe 1-file test suite forFansito the 50-file 9,000-line test suite forAmmonite
If you use uTest and like it, please support it by donating to our Patreon:
- Getting Started
- Defining and Running a Test Suite
- Smart Asserts
- Test Utilities
- Configuring uTest
- Scala.js and Scala-Native
- Running uTest Standalone
- Why uTest
- Changelog
Most people coming to uTest will be running tests throughSBT. Add the following to yourbuild.sbt
and youcan immediately begin defining and running tests programmatically.
libraryDependencies+="com.lihaoyi"%%"utest"%"0.8.5"%"test"testFrameworks+=newTestFramework("utest.runner.Framework")
To use it with Scala.js or Scala-Native:
libraryDependencies+="com.lihaoyi"%%%"utest"%"0.8.5"%"test"testFrameworks+=newTestFramework("utest.runner.Framework")
For Scala-Native, you will also need
nativeLinkStubs:=true
Put this in yoursrc/test/scala/
folder:
packagetest.utest.examplesimportutest._objectHelloTestsextendsTestSuite{valtests=Tests{ test("test1"){thrownewException("test1") } test("test2"){1 } test("test3"){vala=List[Byte](1,2) a(10) } }}
You can then run this via
sbt myproject/test
Which should produce this output:
-------------------------------- Running Tests --------------------------------Setting up CustomFrameworkX test.utest.examples.HelloTests.test1 4ms java.lang.Exception: test1 test.utest.examples.HelloTests$.$anonfun$tests$2(HelloTests.scala:7)+ test.utest.examples.HelloTests.test2.inner 0ms 1X test.utest.examples.HelloTests.test3 0ms java.lang.IndexOutOfBoundsException: 10 scala.collection.LinearSeqOptimized.apply(LinearSeqOptimized.scala:63) scala.collection.LinearSeqOptimized.apply$(LinearSeqOptimized.scala:61) scala.collection.immutable.List.apply(List.scala:86) test.utest.examples.HelloTests$.$anonfun$tests$5(HelloTests.scala:16)Tearing down CustomFrameworkTests: 3, Passed: 1, Failed: 2
The tests are run one at a time, and any tests that fail with an exception havetheir stack trace printed. If the number of tests is large, a separateresults-summary and failures-summary will be shown after all tests have run.
Note that tests within the suite can nested within each other, but onlydirectly. E.g. you cannot define tests withinif
-statements orfor
-loops.uTest relies on the test structure to be statically known at compile time. Theycan be nested arbitrarily deep:
packagetest.utest.examplesimportutest._objectNestedTestsextendsTestSuite{valtests=Tests{valx=1 test("outer1"){valy= x+1 test("inner1"){ assert(x==1, y==2) (x, y) } test("inner2"){valz= y+1 assert(z==3) } } test("outer2"){ test("inner3"){ assert(x>1) } } }}
Here, we have a tree of nested blocks, with three tests at the inner-most blocksof this tree:innerest
,inner2
andinner3
. Test blocks can be nestedarbitrary deeply to help keep things neat, and only the inner-most blocks areconsidered to be tests.
When this suite is run withsbt myproject/test
, it is those three tests that get executed:
------------------------------- Running Tests -------------------------------+ test.utest.examples.NestedTests.outer1.inner1 21ms (1,2)+ test.utest.examples.NestedTests.outer1.inner2 0ms+ test.utest.examples.NestedTests.outer2.inner3 0ms
You can see also thattest.utest.examples.NestedTests.outer1.inner1
displaysthe value of(x, y)
returned from the test:(1,2)
. Returning a value from atest is useful if you want to skim the test's results after a run to performmanual sanity-checks on some computed value.
If you find yourself wanting to define a test in a for, loop, e.g.
// Doesn't work!valtests=Tests{for(fileName<-Seq("hello","world","i","am","cow")){ fileName- {// lots of code using fileName } }}
You can instead factor out the common code into a function, and call that fromeach distinct test case:
// Works!valtests=Tests{defrunTestChecks(fileName:String)= {// lots of code using fileName } test("hello"){ runTestChecks("hello") } test("world"){ runTestChecks("world") } test("i"){ runTestChecks("i") } test("am"){ runTestChecks("am") } test("cow"){ runTestChecks("cow") }}
Or even using theTestPath that is available implicitly to everytest, you can remove the duplication between the test name and the call torunTestChecks()
:
#Also works!valtests=Tests{defrunTestChecks()(implicitpath: utest.framework.TestPath)= {valfileName= path.value.last// lots of code using fileName } test("hello"){ runTestChecks() } test("world"){ runTestChecks() } test("i"){ runTestChecks() } test("am"){ runTestChecks() } test("cow"){ runTestChecks() }}
Apart from running all tests at once using
sbt myproject/test
You can also run individual tests using their full path e.g.
sbt'myproject/test-only -- test.utest.examples.NestedTests.outer1.inner1'sbt'myproject/test-only -- test.utest.examples.NestedTests.outer2.inner2'sbt'myproject/test-only -- test.utest.examples.NestedTests.outer2.inner3'
You can also wrap the test selector in double quotes, which lets you run testwhose path segments contain spaces or other special characters:
sbt'myproject/test-only -- "test.utest.examples.NestedTests.segment with spaces.inner"'
You can run groups of tests by providing the path to the block enclosing all ofthem:
# runs both tests `inner1` and `inner2`sbt'myproject/test-only -- test.utest.examples.NestedTests.outer1'# runs all tests in the `NestedTests` test suitesbt'myproject/test-only -- test.utest.examples.NestedTests'# runs all tests in `NestedTests` and any other suites within `test.utest.examples`sbt'myproject/test-only -- test.utest.examples'
You can also use the{foo,bar}
syntax to specify exactly which tests you wouldlike to run:
# runs both tests `inner2` and `inner3`, explicitlysbt'myproject/test-only -- test.examples.NestedTests.outer1.{inner1,inner2}'# runs `outer1.inner1` and `outer2.inner3` but not `outer1.inner2`sbt'myproject/test-only -- test.examples.NestedTests.{outer1.inner1,outer2.inner3}'# also runs `inner1` and `innerest` (and any other test inside `inner1`) but not `inner2`sbt'myproject/test-only -- test.examples.NestedTests.{outer1.inner1,outer2}'
The same syntax can be used to pick and choose specificTestSuite
s to run, ortests within those test suites:
# Run every test in `HelloTests` and `NestedTests`sbt'myproject/test-only -- test.examples.{HelloTests,NestedTests}'# Runs `HelloTests.test1`, `NestedTests.outer1.inner2` and `NestedTests.outer2.inner3`sbt'myproject/test-only -- test.examples.{HelloTests.test1,NestedTests.outer2}'sbt'myproject/test-only -- {test.examples.HelloTests.test1,test.examples.NestedTests.outer2}'
In general, it doesn't really matter if you are running individual tests, groupsof tests within aTestSuite
, individualTestSuite
s, or packages containingTestSuite
. These all form one a single large tree of tests that you can run,using the same uniform syntax.
By default, SBT runs multiple test suites in parallel, so the output fromthose suites may be interleaved. You can setparallelExecution in Test := false
in your SBT config to make the tests execute sequentially, so the output fromeach suite will be grouped together in the terminal.
uTest defaults to emitting ANSI-colored terminal output describing the test run.You can configure the colors byoverriding methods on your test suite, or disable italtogether withoverride def formatColor = false
.
As you saw in the previous section, you can define blocks tests to group themtogether, and have them share common initialization code in the enclosing block.You can also definemutable values, or "fixtures" in the shared initializationcode, and each nested test with aTests
block gets its own copy of any mutablevariables defined within it:
packagetest.utest.examplesimportutest._objectSeparateSetupTestsextendsTestSuite{valtests=Tests{varx=0 test("outer1"){ x+=1 test("inner1"){ x+=2 assert(x==3)// += 1, += 2 x } test("inner2"){ x+=3 assert(x==4)// += 1, += 3 x } } test("outer2"){ x+=4 test("inner3"){ x+=5 assert(x==9)// += 4, += 5 x } } }}
Here, you can see that thex
available in each inner test block (inner1
,inner2
,inner3
) is separate and independent: each test gets a new copy ofx
, modified only by that test's enclosing blocks. This helps avoid inter-testinterference (where a test ends up implicitly depending on some stateinitialized by another test running earlier in the block) while still making itconvenient to share common setup code between the various tests in your suite.
If you want the mutable fixtures to really-truly be shared between individualtests (e.g. because they are expensive to repeatedly initialize) define itoutside theTests{}
block in the enclosing object:
packagetest.utest.examplesimportutest._objectSharedFixturesTestsextendsTestSuite{varx=0valtests=Tests{ test("outer1"){ x+=1 test("inner1"){ x+=2 assert(x==3)// += 1, += 2 x } test("inner2"){ x+=3 assert(x==7)// += 1, += 2, += 1, += 3 x } } test("outer2"){ x+=4 test("inner3"){ x+=5 assert(x==16)// += 1, += 2, += 1, += 3, += 4, += 5 x } } }}
Here you see that the changes tox
are being shared between the invocations ofall the tests. If you are initializing something expensive to share betweentests, this allows you to avoid paying that cost multiple times, but you need tobe careful the tests aren't mutating shared state that could cause other teststo fail!
You can also use thetest("symbol") -
syntax, if your tests are simply forwardingto a separate helper method to do the real testing:
test("test1")- processFileAndCheckOutput("input1.txt","expected1.txt")test("test2")- processFileAndCheckOutput("input2.txt","expected2.txt")test("test3")- processFileAndCheckOutput("input3.txt","expected3.txt")
Thetest("string"){...}
andtest("symbol")...
syntaxes are equivalent.
The last way of defining tests is with theutest.*
symbol, e.g. these testsfrom theFansiproject:
test("parsing"){defcheck(frag: fansi.Str)= {valparsed= fansi.Str(frag.render) assert(parsed== frag) parsed } test{ check(fansi.Color.True(255,0,0)("lol")) } test{ check(fansi.Color.True(1,234,56)("lol")) } test{ check(fansi.Color.True(255,255,255)("lol")) } test{ (for(i<-0 to255)yield check(fansi.Color.True(i,i,i)("x"))).mkString } test{ check("#"+ fansi.Color.True(127,126,0)("lol")+"omg"+ fansi.Color.True(127,126,0)("wtf") ) } test{ check(square(for(i<-0 to255)yield fansi.Color.True(i,i,i))) }}
Tests defined using the*
symbol are give the numerical names "0", "1", "2",etc.. This is handy if you have a very large number of very simple test cases,don't really care what each one is called, but still want to be able to run themand collect their result separately.
valtests=Tests { test("testSuccess"){Future { assert(true) } } test("testFail"){Future { assert(false) } } test("normalSuccess"){ assert(true) } test("normalFail"){ assert(false) }}TestRunner.runAsync(tests).map { results=>valleafResults= results.leaves.toSeq assert(leafResults(0).value.isSuccess)// root assert(leafResults(1).value.isSuccess)// testSuccess assert(leafResults(2).value.isFailure)// testFail assert(leafResults(3).value.isSuccess)// normalSuccess}
You can have tests which return (have a last expression being) aFuture[T]
instead of a normal value. You can run the suite using.runAsync
to return aFuture
of the results, or you can continue using.run
which will wait forall the futures to complete before returning.
In Scala.js, calling.run
on a test suite with futures in it throws an errorinstead of waiting, since you cannot wait for asynchronous results in Scala.js.
When running the test suites from SBT, you do not need worry about any of thisrun
vsrunAsync
stuff: the test runner will handle it for you and providethe correct results.
valx=1valy="2"assert( x>0, x== y)// utest.AssertionError: x == y// x: Int = 1// y: String = 2
uTest comes with a macro-powered smartassert
s that provide useful debugginginformation in the error message. These take one or more boolean expressions,and when they fail, will print out the names, types and values of any localvariables used in the expression that failed. This makes it much easier to seewhat's going on than Scala's defaultassert
, which gives you the stack traceand nothing else.
uTest also wraps any exceptions thrown within the assert, to help trace whatwent wrong:
valx=1Lvaly=0Lassert(x/ y==10)// utest.AssertionError: assert(x / y == 10)// caused by: java.lang.ArithmeticException: / by zero// x: Long = 1// y: Long = 0
The origin exception is stored as thecause
of theutest.AssertionError
, sothe original stack trace is still available for you to inspect.
uTest's smart asserts live on theutest
package, and are brought into scopeviaimport utest._
. If you want to continue using the built-in Scala asserts,e.g. if you want custom messages if the assert fails, those remain available asPredef.assert
.
1==>1// passesArray(1,2,3)==>Array(1,2,3)// passestry{1==>2// throws}catch{casee: java.lang.AssertionError=> e}
You can usea ==> b
as a shorthand forassert(a == b)
. This results inpretty code you can easily copy-paste into documentation.
vale= intercept[MatchError]{ (0:Any)match {case_:String=> }}println(e)// scala.MatchError: 0 (of class java.lang.Integer)
intercept
allows you to verify that a block raises an exception. Thisexception is caught and returned so you can perform further validation on it,e.g. checking that the message is what you expect. If the block does not raiseone, anAssertionError
is raised.
As withassert
,intercept
adds debugging information to the error messagesif theintercept
fails or throws an unexpected Exception.
valx=Seq(12)eventually(x==Nil)// utest.AssertionError: eventually(x == Nil)// x: Seq[Int] = List(12)
In addition to a macro-poweredassert
, uTest also provides macro-poweredversions ofeventually
andcontinually
. These are used to test asynchronousconcurrent operations:
eventually(tests: Boolean*)
: ensure that the boolean values oftests
allbecome true at least once within a certain period of time.continually(tests: Boolean*)
: ensure that the boolean values oftests
allremain true and never become false within a certain period of time.
These are implemented via a retry-loop, with a default retry interval of 0.1second and retries up to a total of 1 second. If you want to change thisbehavior, you can shadow the implicit valuesretryInterval
andretryMax
, forexample this:
implicitvalretryMax=RetryMax(300.millis)implicitvalretryInterval=RetryInterval(50.millis)
Would set the retry-loop to happen every 50ms up to a max of 300ms.
Together, these two operations allow you to easily test asynchronous operations.You can use them to help verify Liveness properties (that condition musteventually be met) and Safety properties (that a condition is never met)
As withassert
,eventually
andcontinually
add debugging information tothe error messages if they fail.
assertMatch(Seq(1,2,3)){caseSeq(1,2)=>}// AssertionError: Matching failed Seq(1, 2, 3)
assertMatch
is a convenient way of checking that a value matches a particularshape, using Scala's pattern matching syntax. This includes support for use of|
or_
orif
-guards within the pattern match. This gives you additionalflexibility over a traditionalassert(a == Seq(1, 2))
, as you can use_
as awildcard e.g. usingassertMatch(a){case Seq(1, _)=>}
to match any 2-itemSeq
whose first item is1
.
As withassert
,assertMatch
adds debugging information to the error messagesif the value fails to match or throws an unexpected Exception while evaluating.
compileError("true * false")// CompileError.Type("value * is not a member of Boolean")compileError("(}")// CompileError.Parse("')' expected but '}' found.")
compileError
is a macro that can be used to assert that a fragment of code(given as a literal String) fails to compile.
- If the code compiles successfully,
compileError
will fail the compilationrun with a message. - If the code fails to compile,
compileError
will return an instance ofCompileError
, one ofCompileError.Type(pos: String, msgs: String*)
orCompileError.Parse(pos: String, msgs: String*)
to represent typecheckererrors or parser errors
In general,compileError
works similarly tointercept
, except it does itschecks (that a snippet of code fails) and errors (if it doesn't fail) atcompile-time rather than run-time. If the code fails as expected, the failuremessage is propagated to runtime in the form of aCompileError
object. You canthen do whatever additional checks you want on the failure message, such asverifying that the failure message contains some string you expect to be there.
ThecompileError
macro compiles the given string in the local scope andcontext. This means that you can refer to variables in the enclosing scope, i.e.the following example will fail to compile because the variablex
exists.
valx=0compileError("x + x"),// [error] compileError check failed to have a compilation error
The returnedCompileError
object also has a handy.check
method, which takesa position-string indicating where the error is expected to occur, as well aszero-or-more messages which are expected to be part of the final error message.This is used as follows:
compileError("true * false").check("""compileError("true * false").check( ^""","value * is not a member of Boolean")
Note that the position-string needs to exactly match the line of code thecompile-error occured on. This includes any whitespace on the left, as well asany unrelated code or comments sharing the same line as thecompileError
expression.
uTest provides a range of test utilities that aren't strictly necessary, but aimto make your writing of tests much more convenient and DRY.
packagetest.utest.examplesimportutest._objectTestPathTestsextendsTestSuite{valtests=Tests{'testPath{'foo { assert(implicitly[utest.framework.TestPath].value==Seq("testPath","foo")) } } }}
uTest exposes the path to the current test to the body of the test via theutest.framework.TestPath
implicit. This can be used to print debugginginformation while the test is running ("test foo.bar.baz 50% done") or toavoid repetition between the test name and test body.
One example is the Fastparse test suite, which uses the name of the test toprovide the repository that it needs to clone and parse:
defcheckRepo(filter:String=>Boolean= _=>true) (implicittestPath: utest.framework.TestPath)= {valurl="https://github.com/"+ testPath.value.lastimportsys.process._valname= url.split("/").lastif (!Files.exists(Paths.get("target","repos", name))){ println("CLONING")Seq("git","clone", url,"target/repos/"+name,"--depth","1").! } checkDir("target/repos/"+name, filter)}"lihaoyi/fastparse"- checkRepo()"scala-js/scala-js"- checkRepo()"scalaz/scalaz"- checkRepo()"milessabin/shapeless"- checkRepo()"akka/akka"- checkRepo()"lift/framework"- checkRepo()"playframework/playframework"- checkRepo()"PredictionIO/PredictionIO"- checkRepo()"apache/spark"- checkRepo()
This allows us to keep the tests DRY - avoiding having to repeat the name of therepo in the name of the test for every test we define - as well as ensuring thatthey always stay in sync.
If you need additional metadata such as line-numbers or file-paths or class orpackage names, you can use theSourceCode library's implicits to pullthem in for you.
objectLocalRetryTestsextends utest.TestSuite{valflaky=newFlakyThingdeftests=Tests{ test("hello")- retry(3){ flaky.run } }}
You can wrap individual tests, or even individual expressions, in aretry
block to make them retry a number of times before failing. That is very usefulfor dealing with small points of flakiness within your test suite. Aretry
block simply retries its body up to the specified number of times; the first runthat doesn't throw an exception returns the value returned by that run.
You can also useSuite Retries if you want to configureretries more globally across your test suite.
To configure things which affect an entire test run, and not any individualTestSuite
object, you can create your own subclass ofutest.runner.Framework
and override the relevant methods.
For example, if you need to perform some action (initialize a database, cleanupthe filesystem, etc.) not just per-test but per-run, you can do that by defininga customutest.runner.Framework
and overriding thesetup
andteardown
methods:
classCustomFrameworkextends utest.runner.Framework{overridedefsetup()= { println("Setting up CustomFramework") }overridedefteardown()= { println("Tearing down CustomFramework") }}
And then telling SBT to run tests using the custom framework:
testFrameworks+=newTestFramework("test.utest.CustomFramework"),
This is handy for setup/teardown that is necessary but too expensive to dobefore/after every single test, which would be the case if you usedTest Wrapping to do it.
Apart from setup and teardown, there are other methods onutest.runner.Framework
that you can override to customize:
/** * Override to run code before tests start running. Useful for setting up * global databases, initializing caches, etc.*/defsetup()= ()/** * Override to run code after tests finish running. Useful for shutting * down daemon processes, closing network connections, etc.*/defteardown()= ()/** * How many tests need to run before uTest starts printing out the test * results summary and test failure summary at the end of a test run. For * small sets of tests, these aren't necessary since all the output fits * nicely on one screen; only when the number of tests gets large and their * output gets noisy does it become valuable to show the clean summaries at * the end of the test run.*/defshowSummaryThreshold=30/** * Whether to use SBT's test-logging infrastructure, or just println. * * Defaults to println because SBT's test logging doesn't seem to give us * anything that we want, and does annoying things like making a left-hand * gutter and buffering input by default*/defuseSbtLoggers=falsedefresultsHeader=BaseRunner.renderBanner("Results")deffailureHeader=BaseRunner.renderBanner("Failures")defstartHeader(path:String)=BaseRunner.renderBanner("Running Tests"+ path)
You can control how the output of tests gets printed via overriding methods ontheFramework
class, described above. For example, this snippet overrides theexceptionStackFrameHighlighter
method to select which stack frames in a stacktrace are more relevant to you, so they can be rendered more brightly:
classCustomFrameworkextends utest.runner.Framework{overridedefexceptionStackFrameHighlighter(s:StackTraceElement)= { s.getClassName.contains("utest.") }}
This results in stack traces being rendered as such:
theutest.runner.Framework
provides a wide range of hooks you can use tocustomize how the uTest output is colored, wrapped, truncated or formatted:
defformatColor:Boolean=truedefformatTruncateHeight:Int=15defformatWrapWidth:Int=100defformatValue(x:Any)= testValueColor(x.toString)deftoggledColor(t: ufansi.Attrs)=if(formatColor) telse ufansi.Attrs.EmptydeftestValueColor= toggledColor(ufansi.Color.Blue)defexceptionClassColor= toggledColor(ufansi.Underlined.On++ ufansi.Color.LightRed)defexceptionMsgColor= toggledColor(ufansi.Color.LightRed)defexceptionPrefixColor= toggledColor(ufansi.Color.Red)defexceptionMethodColor= toggledColor(ufansi.Color.LightRed)defexceptionPunctuationColor= toggledColor(ufansi.Color.Red)defexceptionLineNumberColor= toggledColor(ufansi.Color.LightRed)defexceptionStackFrameHighlighter(s:StackTraceElement)=truedefformatResultColor(success:Boolean)= toggledColor(if (success) ufansi.Color.Greenelse ufansi.Color.Red)defformatMillisColor= toggledColor(ufansi.Bold.Faint)
Any methods overriden on your own customFramework
apply to everyTestSuite
that is run. If you want to further customize how a singleTestSuite
's outputis formatted, you can overrideutestFormatter
on that test suite.
Note that uTest uses an internal copy of theFansi library, vendored atutest.ufansi
, in order to avoid any compatibility problems with any of yourother dependencies. You can useufansi
to construct the coloredufansi.Str
sthat these methods require, or you could just return coloredjava.lang.String
objects containing ANSI escapes, created however you like, and they will beautomatically parsed into the correct format.
You can mix in theTestSuite.Retries
trait to anyTestSuite
and define theutestRetryCount
int to enable test-level retries for all tests within a suite:
objectSuiteRetryTestsextendsTestSuitewithTestSuite.Retries{overridevalutestRetryCount=3valflaky=newFlakyThingdeftests=Tests{'hello{ flaky.run } }}
You can also useLocal Retries if you want to only retrywithin specific tests or expressions instead of throughout the entire suite.
uTest offers theutestBeforeEach
andutestAfterEach
methods that you canoverride on anyTestSuite
, these methods are invoked before and after runningeach test.
defutestBeforeEach(path:Seq[String]):Unit= ()defutestAfterEach(path:Seq[String]):Unit= ()
These are equivalent toutestWrap
but easier to use for simple cases.
packagetest.utest.examplesimportutest._objectBeforeAfterEachTestextendsTestSuite {varx=0overridedefutestBeforeEach(path:Seq[String]):Unit= { println(s"on before each x:$x") x=0 }overridedefutestAfterEach(path:Seq[String]):Unit= println(s"on after each x:$x")valtests=Tests{ test("outer1"){ x+=1 test("inner1"){ x+=2 assert(x==3)// += 1, += 2 x } test("inner2"){ x+=3 assert(x==4)// += 1, += 3 x } } test("outer2"){ x+=4 test("inner3"){ x+=5 assert(x==9)// += 4, += 5 x } } }}
-------------------------------- Running Tests --------------------------------Setting up CustomFrameworkon before each x: 0on after each x: 3+ test.utest.examples.BeforeAfterEachTest.outer1.inner1 22ms 3on before each x: 3on after each x: 4+ test.utest.examples.BeforeAfterEachTest.outer1.inner2 1ms 4on before each x: 4on after each x: 9+ test.utest.examples.BeforeAfterEachTest.outer2.inner3 0ms 9Tearing down CustomFrameworkTests: 3, Passed: 3, Failed: 0
BothutestBeforeEach
andutestAfterEach
runs insideutestWrap
'sbody
callback.
If you need something fancier than whatutestBeforeEach
orutestAfterEach
provide, e.g. passing initialized objects into the main test case or tearingthem down after the test case has completed, feel free to define your testwrapper/initialization function and use it for each test case:
defmyTest[T](func:Int=>T)= {valfixture=1337// initialize some valuevalres= func(fixture)// make the value available in the test case assert(fixture==1337)// properly teardown the value later res}test("test")- myTest{ fixture=>// do stuff with fixture}
The abovemyTest
function can also take aTestPath implicit if itwants access to the current test's path.
If you're looking for something likeutestBeforeAll
, you can add your code tothe object body, and you can also use lazy val to delay the initialization untilthe test suite object is created.
uTest offers theutestAfterAll
method that you can override on anytest suite, this method is invoked after running the entire test suite.
defutestAfterAll():Unit= ()
packagetest.utest.examplesimportutest._objectBeforeAfterAllSimpleTestsextendsTestSuite { println("on object body, aka: before all")overridedefutestAfterAll():Unit= { println("on after all") }valtests=Tests { test("outer1"){ test("inner1"){1 } test("inner2"){2 } } }}
-------------------------------- Running Tests --------------------------------Setting up CustomFrameworkon object body, aka: before all+ test.utest.examples.BeforeAfterAllSimpleTests.outer1.inner1 2ms 1+ test.utest.examples.BeforeAfterAllSimpleTests.outer1.inner2 0ms 2on after all
uTest exposes theutestWrap
function that you can override on any test suite:
defutestWrap(path:Seq[String],runBody:=> concurrent.Future[Any]) (implicitec:ExecutionContext): concurrent.Future[Any]
This is a flexible function that wraps every test call; you can use it to:
- Perform initialization before evaluating
runBody
- Perform cleanup after the completion of
runBody
viarunBody.onComplete
- Perform retries by executing
runBody
multiple times - Log the start and end times of each test, along with the
path
of that test,e.g.Seq("outer", "inner1", "innerest")
for the testouter.inner1.innerest
Generally, if you want to perform messy before/after logic around everyindividual test, overrideutestWrap
. Please remember to call theutestBeforeEach
andutestAfterEach
methods when needed.
runBody
is a future to support asynchronous testing, which is the only way totest things like Ajax calls inScala.js
uTest is completely compatible with Scala.js and Scala-Native: the above sections ondefining a test suite, asserts and the test-running API all work unchanged underScalaJS, with minor differences:
- ScalaJS does not support parallelism, and as such only single-threaded
ExecutionContexts
likeutest.ExecutionContext.runNow
orscala.scalajs.concurrent.JSExecutionContext.runNow
work. When run via SBT,--parallel
has no effect. - Eventually and Continually are not supported,as they rely on a blocking retry-loop whereas you can't block in ScalaJS.
Apart from these differences, there should be no problem compiling uTestTestSuites via Scala.js and running them on Node.js, in the browser, or (withScala-Native) on LLVM.
Note that Scala-Native support, like Scala-Native, is experimental. While it istested in a few projects (uTest's own test suite runs in Scala-Native), it doeshave it's own quirks (e.g.NullPointerException
s appear to be fatal) and doesnot have the weight of third-party usage that the Scala-JVM and Scala.jsversions of uTest have.
uTest exposes a straightforward API that allows you to run tests, receiveresults and format them in a nice way when used standalone, without using SBT'stest integration. The following code snippet demonstrates how to define testsand run them directly:
importutest._valtests=Tests{ test("test1"){// throw new Exception("test1") } test("test2"){ test("inner"){1 } } test("test3"){vala=List[Byte](1,2)// a(10) }}// Run and return resultsvalresults1=TestRunner.run(tests)
TheTestRunner.run
call can be customized with additional arguments to controlhow the code is run, or can be replaced with.runAsync
to handle asynchronoustesting, or.runAndPrint
/.runAndPrintAsync
if you want to print the resultsof tests as they complete using the normal output formatter:
// Run, return results, and print streaming output with the default formattervalresults2=TestRunner.runAndPrint( tests,"MyTestSuiteA")// Run, return results, and print output with custom formatter and executorvalresults3=TestRunner.runAndPrint( tests,"MyTestSuiteA", executor=new utest.framework.Executor{overridedefutestWrap(path:Seq[String],runBody:=>Future[Any]) (implicitec:ExecutionContext):Future[Any]= { println("Getting ready to run"+ path.mkString(".")) utestBeforeEach() runBody.andThen {case _=> utestAfterEach() } } }, formatter=new utest.framework.Formatter{overridedefformatColor=false })
Lastly, you can also runTestSuite
objects in the same way:
// Run `TestSuite` object directly without using SBT, and use// its configuration for execution and output formattingobjectMyTestSuiteextendsTestSuite{valtests=Tests{ test("test1"){// throw new Exception("test1") } test("test2"){ test("inner"){1 } } test("test3"){vala=List[Byte](1,2)// a(10) } }}valresults4=TestRunner.runAndPrint(MyTestSuite.tests,"MyTestSuiteB", executor=MyTestSuite, formatter=MyTestSuite)
uTest provides helpers to render the standard summary reports at the end of atest run, once all your results are in:
// Show summary and exitval (summary, successes, failures)=TestRunner.renderResults(Seq("MySuiteA"-> results1,"MySuiteA"-> results2,"MySuiteA"-> results3,"MySuiteB"-> results4 ))if (failures>0) sys.exit(1)
You can thus use uTest anywhere you can run Scala code, even when not using SBT:in a normalmain
method, withinAmmonite scripts, orelsewhere.
uTest aims to give you the things that everyone needs to run tests, giving youone obvious way to do common tasks while testing. uTest is simple and compact,letting you avoid thinking about it so you can focus on what's most important:you tests.
Hence uTest provides:
A simple, uniform syntax fordelimiting tests and grouping tests together as blocks:
test("test"){ ... }
A simple, uniform syntax forrunning tests:
foo.BarTests
,foo.BarTests.baz.qux
,foo.BarTests.{baz,qux}
orfoo.{BarTests,BazTests}
A simple, uniform syntax forSmart Asserts, that save youthe day-to-day drudgery of printing out the value of variables when thingsfail
Isolation of tests within the same suite,to avoid inter-test interference due to mutable state
uTest tries to provide things that every developer needs, in their minimal,essential form. It intentionally avoids redundant/unnecessary features orsyntaxes that bloat the library and make it harder to developers to pick up,which I find to be common in other popular testing libraries likeScalatest orSpecs2:
Fluent English-like code: matchers like
shouldBe
orshould not be
ormustbe_==
don't really add anything, and it doesn't really matter whether you name eachtest block usingshould
,when
,can
,must
,feature("...")
orit should "..."
Multiple redundant waysof defining test suites, individual tests and blocks of related tests
Legacy code, like ScalaTeststimepackage, nowobsolete with the introduction ofscala.concurrent.duration.
While uTest has and will continue to slowly grow and add more features, it isunlikely that it will ever reach the same level of complexity that other testinglibraries are currently at.
- Run
Future
tests sequentially, not concurrently#359 - Minimum version of Java bumped from 8 to 11
- Avoid crashing if test logs have invalid ANSI escape codes#344
- Support for Scala-Native 0.5.0
- Fix compiler warning when using
utest.framework.TestPath
#309
- Add
++
toTests
so that test suites can be concatenated - Add
.prefix(name: String)
toTests
to nest all of its tests under a single test group with a given name
- Drop support for Scala.js 0.6
- Bump Scala.js to 1.10 (minimum version supported is 1.8)
- Bump Scala versions to latest (2.12.16, 2.13.8, 3.1.3)
- Add support for Scala 3 on Scala Native
- Add support for Scala 3.0.0
- Add support for Scala 3.0.0-RC3
- Bump Scala.js from 1.4.0 to 1.5.1
- Add support for Scala 3.0.0-RC2
- Support Scala 3 on Scala.js
- Re-publish to maven central only. The version 0.7.6 broke binary compatibilitywith JDK 8.
- Support for Scala-Native 0.4.x
- Add support for Scala.js 1.0.0
- Add support for Scala.js 1.0.0-RC2
- Add support for Scala.js 1.0.0-RC1
- Changed test syntax to
test("foo"){...}
,test{...}
. Old syntax is nowdeprecated.
- Added support for Scala 2.13.0 Final
- Dropped support for Scala 2.10.x, 2.11.x
- Temporarily dropped support for Scala.js 1.0.0-M8, Scala-native 0.4.0-M2
- Add support for Scala 2.13.0-RC1
- Use IndexedSeq instead of Array in
Tests
macro
- Add support for Scala 2.13.0-M5
- Upgrade Scala 2.13.0-M2 to 2.13.0-M3
- Upgrade Scala.JS 0.6.22 to 0.6.25
- Upgrade Scala.JS 1.0.0-M3 to 1.0.0-M5
- Upgrade Scala Native 0.3.6 to 0.3.7
- Replace Scala.JS' deprecated
TestUtils
with portable-scala-reflect
- Bugfix where sometimes all tests would pass but report as failed (thanks @eatkins)
- By default, don't cut-off and wrap output
- (Hopefully) fix intermittent
IllegalStateException: Unknown opcode 5
exceptions occuring with Scala.JS.
- Returning
null
from a test case no longer blows up - Added
utest.runner.MillFramework
- Added support for Scala.js 1.0.0-M2.
- Fix cross-publishing for Scala.js, which was borked in 0.6.0
- Ensure utestAfterEach be executed regardless of a test failure
Migrate formatting-related configuration options from
utest.TestSuite
ontotheutest.runner.Framework
, as describedhere.Added the
exceptionStackFrameHighlighter
formatting hook, letting you choosewhich stack frames in an exception are of interest to you so they can berendered more brightly.
- Added hooks forRunning code before and after test casesandRunning code before and after test suites,thanks toDiego Alvarez
Cross-publish for Scala 2.13.0-M2, Scala.js 0.6.20, Scala-Native 0.3.3
Stack traces for chained exceptions (i.e. those with a
.getCause != null
)are now properly displayed when tests failPortions of stack traces caused by the internals of the
assert
macros arenow hidden, since they aren't relevant to any failure in user codeRevamped test output format, motivated bydrhumlen'sPR 113, which should be mucheasier to read and skim
Much smarter test-output-value truncation, now based on lines-of-output(including wrapping) rather than number-of-characters
Line-wrapping of output is now indentation aware: it wraps to the next linewith the same indentation, preserving the outline of indentation on the leftmaking it easier to skim
How long tests take (im milliseconds) is now displayed in the standard testoutput format
Hierarchical test summary and failure-summary are now only shown when you havea large number of tests, since it's not use when running individual or smallnumbers of tests. The default threshold is 20, which can be configured bydefining a custom framework overriding
showSummaryThreshold
Revamped test-query system, now allowing you to run multiple groups of testsat once via
test-only -- mypackage.{foo,bar,baz}
or{mypackage, myotherpackage}
ormypackage.{foo,bar.{baz,qux}}
Overhauled the execution model of test suites: now only the inner-most blockswithin your
TestSuite{...}
block count as tests, and the surrounding blocksdo not. Thus the surrounding blocks no longer show pass/fail status, return atest result-value, or get run independently.Various user errors (non-existent test, non-existing test suite, invalid testquery syntax) now give slightly more friendly error messages
Using uTest with
fork in Test := true
in SBT no longer gives an incorrectresults summmaryFix problem with lazy vals in test blocks crashing the compiler#67. Note that the issue is onlyfixed on 2.12.3, and not on Scala 2.10.x or 2.11.x.
Deprecated the
'foo{...}
syntax for defining tests. We should standardize ontest("foo"){...}
ortest("foo"){...}
The
TestSuite{...}
for defining a block of tests is now deprecated; it neveractually defined aTestSuite
object, so the name was pretty misleading. Thenew syntax isTests{...}
Remove the
MultipleErrors
wrapper when you test more than one expression inanassert
and multiple of them fail. Now only the first expression in theassert
call which fails will have it's exception thrown.Movedsmart asserts from being embedded into the
TestSuite
objects directly onto theutest
package, so they are pulled in by theimport utest._
import.
- Scala Native support.
- Scala 2.13 support
- Compile for Scala 2.12 with method optimisations (
-opt:l:method
)
- Upgrade Scala.JS to 0.6.16.
- Upgrade Scala to 2.11.11 and 2.12.2.
- Avoid using
getStackTraceString
deprecated in Scala 2.13
- Catch Fatal exceptions like ClassCasts in Scala.JS.
- Scala 2.12 support
- Generalize
==>
asserts to work onArray
s - Shiny newCI build andGitter Chat
- Move errors into
utest.*
fromutest.framework.*
- Removed
acyclic
hack variable
- Fix usage of by-name function calls within tests #55
foo: @Show
annotation lets you tell uTest to print out arbitrary expressionswithin the test suite when things fail, in addition to the default of localvariablesYou can use
a ==> b
to assert equality within a test suite, in a form that'spretty enough to use as documentationcompileError
now properly passes when an expression would fail to compiledue to@compileTimeOnly
annotationsConfiguring uTest has been overhauled.
Scala
2.12.0-M3
supportFix some warnings appearing when the
-Ydead-code
flag is usedAddedTestPath implicit to make the path to the current testavailable for usage inside the test body or helpers.
- Published for Scala.js 0.6.1
- Published for Scala.js 0.6.0
- Removed
JsCrossBuild
now that Scala.js supports cross-building viacrossProject
compileTimeOnly
has been re-introduced, so invalid use of test DSL shouldfail with nice errors- Removed
--throw
flag in favor of "native" SBT error reporting
- Added support for asynchronous tests which return a
Future
.
- Updated to work against ScalaJS 0.5.4
- Introduced
CompileError.check(pos: String, msgs: String*)
to simplify thecommon pattern of checking that the error occurs in the right place and withthe message you expect. - Changed the file layout expected by
JsCrossBuild
, to expect the shared filesto be injs/shared/
andjvm/shared/
, rather than inshared/
. This istypically achieved via symlinks, and should make the cross-build play muchmore nicely with IDEs.
- Fix bug in
utestJsSettings
pulling in the wrong version of uTest
- Introduced the
compileError
macro to allow testing of compilation errors. - Stack traces are now only shown for the user code, with the uTest/SBT internalstack trace ignored, making them much less spammy and noisy.
- Introduced the
*
symbol, which can be used in place of a test name to getsequentially numbered test names.
- ScalaJS version is now built against ScalaJS 0.5.3
- Fixed linking errors in ScalaJS version, to allow proper operation of the newoptimization
- Fixed bug causing local-defs in assert macro to fail
- Extracted out
utestJvmSettings
andutestJsSettings
for use outside theJsCrossBuild
plugin, for people who don't want to use the plugin.
- Print paths of failing tests after completion to make C&P-ing re-runs moreconvenient
About
A simple testing framework for Scala