Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ScalaTest - writing custom matchers

I am running into a problem while writing a custom matcher for NodeSeq:

private def matchXML(expected: NodeSeq) = new Matcher[NodeSeq] {
  def apply(left: NodeSeq): MatchResult = MatchResult(left xml_== expected,
    "XML structure was not the same (watch spaces in tag texts)",
    "XML messages were equal")
}

This compiles, but the following piece of code:

val expected : NodeSeq = ...
val xml : NodeSeq = ... 
xml should matchXML(expected)

causes:

error: overloaded method value should with alternatives:
(beWord: XMLStripDecoratorTests.this.BeWord)XMLStripDecoratorTests.this.ResultOfBeWordForAnyRef[scala.collection.GenSeq[scala.xml.Node]] <and>
(notWord: XMLStripDecoratorTests.this.NotWord)XMLStripDecoratorTests.this.ResultOfNotWordForAnyRef[scala.collection.GenSeq[scala.xml.Node]] <and>
(haveWord: XMLStripDecoratorTests.this.HaveWord)XMLStripDecoratorTests.this.ResultOfHaveWordForSeq[scala.xml.Node] <and>
(rightMatcher: org.scalatest.matchers.Matcher[scala.collection.GenSeq[scala.xml.Node]])Unit
cannot be applied to (org.scalatest.matchers.Matcher[scala.xml.NodeSeq])
xml should (matchXML(expected))

Any ideas what this means?

like image 983
Bober02 Avatar asked May 02 '13 15:05

Bober02


People also ask

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.

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.

What is a fixture in ScalaTest?

Regarding to the ScalaTest documentation: A test fixture is composed of the objects and other artifacts (files, sockets, database connections, etc.) tests use to do their work. When multiple tests need to work with the same fixtures, it is important to try and avoid duplicating the fixture code across those tests.


1 Answers

Why this fails to typecheck:

The type checker works in the following way.

xml.should(matchXML(expected))
  • Because the method should is not part of a NodeSeq, the compiler tries to find an implicit conversion for xml to a ShouldMatcher. The book "Programming in Scala" specifies that such implicit conversion should be the most specific:

"Up through Scala 2.7, that was the end of the story. Whenever multiple implicit conversions applied, the compiler refused to choose between them. ... Scala 2.8 loosens this rule. If one of the available conversions is strictly more specific than the others, then the compiler will choose the more specific one. ... one implicit conversion is more specific than another if one of the following applies: The argument type of the former is a subtype of the latter’s. .."

  • Because NodeSeq extends Seq[Node], the following function

    convertToSeqShouldWrapper[T](o : scala.GenSeq[T]) : SeqShouldWrapper[T]

    is therefore the most specific one among all others.

The program is rewritten as:

`convertToSeqShouldWrapper(xml).should(matchXML(expected))`

where convertToSeqShouldWrapper(xml) is a SeqShouldWrapper[T] where T = GenSeq[Node].

The method should from SeqShouldWrapper accepts a Matcher[T] which is a function of type T => MatchResult. Therefore, it accepts a Matcher[GenSeq[Node]].

Because T is appearing to the left of the arrow, matchers are not covariant in T, but contravariant. A NodeSeq is a GenSeq[Node], so a Matcher[GenSeq[Node]] is a Matcher[NodeSeq], not the opposite.This explains the above error, where the method should cannot accept a Matcher[NodeSeq] and requires a Matcher[GenSeq[Node]].

2 Solutions

  • Replace All instances of NodeSeq to GenSeq[Node] so that the type matches everywhere.
  • Alternatively, wrap xml explicitely with the conversion function.

    convertToAnyShouldWrapper(xml).should(matchXML(expected))

like image 63
Mikaël Mayer Avatar answered Oct 01 '22 07:10

Mikaël Mayer