Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does ScalaTest syntax work?

Tags:

syntax

scala

I've done some programming in Scala, and I know that, e.g.,

xs map f

is the same thing as

xs.map(f)

but I have no idea how to generalize this syntax to something like ScalaTest's syntax, e.g.,

it should "throw NoSuchElementException if an empty stack is popped" in {
  val emptyStack = new Stack[String]
  evaluating { emptyStack.pop() } should produce [NoSuchElementException]
}

I'm mainly wondering about the things that look like multi-word constructs, namely should produce. It's neat.

like image 771
Yang Avatar asked Oct 13 '09 07:10

Yang


People also ask

What is ScalaTest used for?

ScalaTest is one of the most popular, complete and easy-to-use testing frameworks in the Scala ecosystem. Where ScalaTest differs from other testing tools is its ability to support a number of different testing styles such as XUnit and BDD out of the box.

Should matchers vs must matchers?

Matchers , but uses the verb must instead of should . The two traits differ only in the English semantics of the verb: should is informal, making the code feel like conversation between the writer and the reader; must is more formal, making the code feel more like a written specification.


2 Answers

That kind of syntax is method calls in operator notation, but carried forward more than just three tokens. As you already mentioned:

xs map f

means:

xs.map(f)

But you could go further and say:

xs map f map g

which means:

xs.map(f).map(g)

In ScalaTest matchers, for example, you could say:

result should not be null

That gets desugared by the compiler to:

result.should(not).be(null)

This:

it should "throw an exception" in { ... }

gets desugared into:

it.should("throw an exception").in { ... }

The curly braces at the end is really just a way to pass the code in between the curly braces (the test code) into the in method, wrapped as a no-arg function. So all of these are the same idea. Operator notation used twice in a row.

The last one you asked about is a tad different:

evaluating { ... } should produce [IllegalArgumentException]

This gets transformed into:

evaluating { ... } is, well, evaluated first, because the curly braces give it precedence. So that is a method call, you are calling a method named "evaluating", passing in the code in between the curly braces as a no-arg function. That returns an object, on which should is invoked. So should is a method on the object returned by invoking evaluating. What should actually takes is the result of invoking produce. Here produce is actually a method, which has a type parameter such as [IllegalArgumentException]. It must be done this way so the Scala compiler can "poor-man's-reify" that type parameter. It passes an implicit "Manifest" parameter into produce that can provide the java.lang.Class instance for IllegalArgumentException. When that should method is invoked, therefore, it has a function containing the code passed to evaluating, and a way to find the java.lang.Class of the exception type put in the square brackets. So it executes the block of code wrapped in a try, catches the exception, compares it with what's expected. If no exception is thrown, or the wrong one, the should method throws a TestFailedException. Otherwise the should method just returns silently.

So, the answer is that line gets desugared into:

(evaluating { ... }).should(produce[IllegalArgumentException] (compilerSuppliedManifest))

And the moral of the story is that high level code like this makes it easier to see the programmer's intent, but often harder to understand how the code actually works. Most of the time in practice all you care about is the intent, but now and then you need to know how something works. In such cases in Scala you can pass -Xprint:typer as a command line arg to the Scala compiler and it will print out a version of your file after all the desugaring has happened. So you can see what's what when you need to.

like image 124
Bill Venners Avatar answered Oct 30 '22 06:10

Bill Venners


It's reasonably easy because it's normal code - it should is equivalent to it.should hence there must be a value (or method) called it in scope. And there is!

This variable is of type ItWord which exposes a method called should which takes an object of type BehaveWord. These matchers are mixed in via implicit conversions in the ShouldMatchers trait.

ScalaTest is actually extremely well documented with tons of examples and descriptions of how things work.

like image 30
oxbow_lakes Avatar answered Oct 30 '22 04:10

oxbow_lakes