One can use have to check if property equals to a value.
Is there some way to check the property not for equality, but to check if it satisfies a matcher?
Following compiles, but unsurprisingly it does not work, as the property is tested for equality with the matcher value.
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class MainTest extends AnyFlatSpec with Matchers {
case class Book(title: String, author: List[String], pubYear: Int)
"Something" should "work" in {
val book = Book("Programming in Scala", List("Odersky", "Spoon", "Venners"), 2008)
book should have (
Symbol("title") ("Programming in Scala"),
Symbol("pubYear") (be >= 2006 and be <= 2010)
)
}
}
You re looking for matchPattern:
book should matchPattern {
case Book("Programming in Scala", _, year) if 2006 to 2010 contains year =>
}
Consider Inside which allows you to assert statements about case class properties using pattern matching:
"Something" should "work" in {
inside (book) { case Book(title, _, pubYear) =>
title should be ("Programming in Scala")
pubYear should (be >= 2008 and be <= 2010)
}
}
Related
In ScalaTest, I have the following check:
"abc".r shouldBe "abc".r
But it is not equal. I don't understand.
abc was not equal to abc
ScalaTestFailureLocation: com.ing.cybrct.flink.clickstream.ConfigsTest$$anonfun$6 at (ConfigsTest.scala:97)
Expected :abc
Actual :abc
While it's possible to decide whether two regular expressions accept the same language, it seems to be rather complicated and not all that terribly useful for everyday regex usage. Therefore, equality on compiled regex patterns is just referential equality:
val x = "abc".r
val y = "abc".r
x == y
// res0: Boolean = false
The method shouldBe in Scalatest 3.0.5 delegates the equality check to the areEqualComparingArraysStructurally method:
def shouldBe(right: Any): Assertion = {
if (!areEqualComparingArraysStructurally(leftSideValue, right)) {
val (leftee, rightee) = Suite.getObjectsForFailureMessage(leftSideValue, right)
val localPrettifier = prettifier // Grabbing a local copy so we don't attempt to serialize AnyShouldWrapper (since first param to indicateFailure is a by-name)
indicateFailure(FailureMessages.wasNotEqualTo(localPrettifier, leftee, rightee), None, pos)
}
else indicateSuccess(FailureMessages.wasEqualTo(prettifier, leftSideValue, right))
}
which in turn simply delegates the equality check (as you can expect) to the == operator:
private[scalatest] def areEqualComparingArraysStructurally(left: Any, right: Any): Boolean = {
// Prior to 2.0 this only called .deep if both sides were arrays. Loosened it
// when nearing 2.0.M6 to call .deep if either left or right side is an array.
// TODO: this is the same algo as in scalactic.DefaultEquality. Put that one in
// a singleton and use it in both places.
left match {
case leftArray: Array[_] =>
right match {
case rightArray: Array[_] => leftArray.deep == rightArray.deep
case _ => leftArray.deep == right
}
case _ => {
right match {
case rightArray: Array[_] => left == rightArray.deep
case _ => left == right
}
}
}
}
In Scala, at least on the JVM, == simply calls equals, which, if not overridden, checks whether the compared variables point to the same object. case classes are peculiar in that the compiler overrides equals for you to compare the constructor arguments.
You can test it very easily with the following (but as you can imagine, the same applies to simply using == on your own):
package org.example
import org.scalatest.{FlatSpec, Matchers}
final class MyClass(val a: Int)
final case class MyCaseClass(a: Int)
final class TestSpec extends FlatSpec with Matchers {
"equality on case classes" should "succeed" in {
new MyCaseClass(1) shouldBe new MyCaseClass(1)
}
"equality on non-case classes" should "fail" in {
new MyClass(1) shouldNot be(new MyClass(1))
}
"equality between Regex objects" should "fail" in {
"abc".r shouldNot be("abc".r)
}
}
What the r method does is instantiating a new Regex object, which is not a case class and does not override the equality definition, thus yielding the result you see.
Yes, they are not equal. Both "abc".r are different references to two different memory locations. shouldbe is to check equality but not identity.
Some nested case classes and the field addresses is a Seq[Address]:
// ... means other fields
case class Street(name: String, ...)
case class Address(street: Street, ...)
case class Company(addresses: Seq[Address], ...)
case class Employee(company: Company, ...)
I have an employee:
val employee = Employee(Company(Seq(
Address(Street("aaa street")),
Address(Street("bbb street")),
Address(Street("bpp street")))))
It has 3 addresses.
And I want to capitalize the streets start with "b" only. My code is mess like following:
val modified = employee.copy(company = employee.company.copy(addresses =
employee.company.addresses.map { address =>
address.copy(street = address.street.copy(name = {
if (address.street.name.startsWith("b")) {
address.street.name.capitalize
} else {
address.street.name
}
}))
}))
The modified employee is then:
Employee(Company(List(
Address(Street(aaa street)),
Address(Street(Bbb street)),
Address(Street(Bpp street)))))
I'm looking for a way to improve it, and can't find one. Even tried Monocle, but can't apply it to this problem.
Is there any way to make it better?
PS: there are two key requirements:
use only immutable data
don't lose other existing fields
As Peter Neyens points out, Shapeless's SYB works really nicely here, but it will modify all Street values in the tree, which may not always be what you want. If you need more control over the path, Monocle can help:
import monocle.Traversal
import monocle.function.all._, monocle.macros._, monocle.std.list._
val employeeStreetNameLens: Traversal[Employee, String] =
GenLens[Employee](_.company).composeTraversal(
GenLens[Company](_.addresses)
.composeTraversal(each)
.composeLens(GenLens[Address](_.street))
.composeLens(GenLens[Street](_.name))
)
val capitalizer = employeeStreeNameLens.modify {
case s if s.startsWith("b") => s.capitalize
case s => s
}
As Julien Truffaut points out in an edit, you can make this even more concise (but less general) by creating a lens all the way to the first character of the street name:
import monocle.std.string._
val employeeStreetNameFirstLens: Traversal[Employee, Char] =
GenLens[Employee](_.company.addresses)
.composeTraversal(each)
.composeLens(GenLens[Address](_.street.name))
.composeOptional(headOption)
val capitalizer = employeeStreetNameFirstLens.modify {
case 'b' => 'B'
case s => s
}
There are symbolic operators that would make the definitions above a little more concise, but I prefer the non-symbolic versions.
And then (with the result reformatted for clarity):
scala> capitalizer(employee)
res3: Employee = Employee(
Company(
List(
Address(Street(aaa street)),
Address(Street(Bbb street)),
Address(Street(Bpp street))
)
)
)
Note that as in the Shapeless answer, you'll need to change your Employee definition to use List instead of Seq, or if you don't want to change your model, you could build that transformation into the Lens with an Iso[Seq[A], List[A]].
If you are open to replacing the addresses in Company from Seq to List, you can use "Scrap Your Boilerplate" from shapeless (example).
import shapeless._, poly._
case class Street(name: String)
case class Address(street: Street)
case class Company(addresses: List[Address])
case class Employee(company: Company)
val employee = Employee(Company(List(
Address(Street("aaa street")),
Address(Street("bbb street")),
Address(Street("bpp street")))))
You can create a polymorphic function which capitalizes the name of a Street if the name starts with a "b".
object capitalizeStreet extends ->(
(s: Street) => {
val name = if (s.name.startsWith("b")) s.name.capitalize else s.name
Street(name)
}
)
Which you can use as :
val afterCapitalize = everywhere(capitalizeStreet)(employee)
// Employee(Company(List(
// Address(Street(aaa street)),
// Address(Street(Bbb street)),
// Address(Street(Bpp street)))))
Take a look at quicklens
You could do it like this
import com.softwaremill.quicklens._
case class Street(name: String)
case class Address(street: Street)
case class Company(address: Seq[Address])
case class Employee(company: Company)
object Foo {
def foo(e: Employee) = {
modify(e)(_.company.address.each.street.name).using {
case name if name.startsWith("b") => name.capitalize
case name => name
}
}
}
Is there a nice way to check that a pattern match succeeds in ScalaTest? An option is given in scalatest-users mailing list:
<value> match {
case <pattern> =>
case obj => fail("Did not match: " + obj)
}
However, it doesn't compose (e.g. if I want to assert that exactly 2 elements of a list match the pattern using Inspectors API). I could write a matcher taking a partial function literal and succeeding if it's defined (it would have to be a macro if I wanted to get the pattern in the message as well). Is there a better alternative?
I am not 100% sure I understand the question you're asking, but one possible answer is to use inside from the Inside trait. Given:
case class Address(street: String, city: String, state: String, zip: String)
case class Name(first: String, middle: String, last: String)
case class Record(name: Name, address: Address, age: Int)
You can write:
inside (rec) { case Record(name, address, age) =>
inside (name) { case Name(first, middle, last) =>
first should be ("Sally")
middle should be ("Ann")
last should be ("Jones")
}
inside (address) { case Address(street, city, state, zip) =>
street should startWith ("25")
city should endWith ("Angeles")
state should equal ("CA")
zip should be ("12345")
}
age should be < 99
}
That works for both assertions or matchers. Details here:
http://www.scalatest.org/user_guide/other_goodies#inside
The other option if you are using matchers and just want to assert that a value matches a particular pattern, you can just the matchPattern syntax:
val name = Name("Jane", "Q", "Programmer")
name should matchPattern { case Name("Jane", _, _) => }
http://www.scalatest.org/user_guide/using_matchers#matchingAPattern
The scalatest-users post you pointed to was from 2011. We have added the above syntax for this use case since then.
Bill
This might not be exactly what you want, but you could write your test assertion using an idiom like this.
import scala.util.{ Either, Left, Right }
// Test class should extend org.scalatest.AppendedClues
val result = value match {
case ExpectedPattern => Right("test passed")
case _ => Left("failure explained here")
})
result shouldBe 'Right withClue(result.left.get)
This approach leverages the fact that that Scala match expression results in a value.
Here's a more concise version that does not require trait AppendedClues or assigning the result of the match expression to a val.
(value match {
case ExpectedPattern => Right("ok")
case _ => Left("failure reason")
}) shouldBe Right("ok")
I'm trying to replicate the powerful pattern matching example that Joshua Suereth presented in his Devoxx 2013 talk titled "How to wield Scala in the trenches". Unfortunately I cannot achieve what he described and I cannot understand what is wrong. Can someone give me a hint at what I'm missing? (My Scala version is 2.10.3)
Please see the self contained code below:
case class Person(name: String, residence: Seq[Residence])
case class Residence(city: String, country: String)
object LivesIn {
def unapply(p: Person): Option[Seq[String]] =
Some(
for(r <- p.residence)
yield r.city
)
}
class StringSeqContains(value: String) {
def unapply(in: Seq[String]): Boolean =
in contains value
}
object PatternPower extends App {
val people =
Seq(Person("Emre", Seq(Residence("Antwerp", "BE"))),
Person("Ergin", Seq(Residence("Istanbul", "TR"))))
val Istanbul = new StringSeqContains("Istanbul")
// #1 does not work as expected, WHY?
println(
people collect {
case person # LivesIn(Istanbul) => person
}
)
// #2 works as expected
println(
people collect {
case person # LivesIn(cities) if cities.contains("Istanbul") => person
}
)
// #3 works as expected
println(
people collect {
case person # Person(_, res) if res.contains(Residence("Istanbul", "TR")) => person
}
)
}
When I compile and run it I get:
List()
List(Person(Ergin,List(Residence(Istanbul,TR))))
List(Person(Ergin,List(Residence(Istanbul,TR))))
As denoted in the source code, I fail to grasp why the first pattern does not produce the same result as the remaining two pattern matches. Any ideas why?
Your LivesIn extractor requires a Seq for an argument.
The following variation does what you expect:
println(
people collect {
case person # LivesIn(List("Istanbul")) => person
}
)
After some thinking and Googling, I realized that one should add () to the inner extractor (thanks to The Neophyte's Guide to Scala Part 1: Extractors).
In other words, the following works as expected:
people collect {
case person # LivesIn(Istanbul()) => person
}
whereas the following code silently, without any complaints, returns List():
people collect {
case person # LivesIn(Istanbul) => person
}
Unless I'm mistaken in another way (e.g. there is way to make it work without parantheses), I think technical presenters should be more careful with the code snippets / pseudo-code snippets (so that some of the curious audience will not lose sleepless hours ;-)
Lets assume that I have a plain third party (i.e. I cannot modify it) class defined like:
class Price(var value: Int)
Is this possible to match instances of this class with some patterns?
For example, I want to implement function:
def printPrice(price: Price) = {
// implementation here
}
... that prints price is {some value} for every price that has value <= 9000 and price is over 9000 in all other cases.
For example, calling:
printPrice(new Price(10))
printPrice(new Price(9001))
should print:
price is 10
price is over 9000
How can I implement printPrice using pattern matching?
You can create custom extractor:
package external {
class Price(var value: Int)
}
object Price {
def unapply(price: Price): Option[Int] = Some(price.value)
}
def printPrice(price: Price) = price match {
case Price(v) if v <= 9000 => println(s"price is $v")
case _ => println("price is over 9000")
}
printPrice(new Price(10))
printPrice(new Price(9001))
For case classes compiler generates it automaticaly. I think in your case extractors is overkill, but may be it's only simplified sample.
Thinked about accepting flavian's solution but came up with slightly better one by myself.
Here is how one could implement printPrice (without need to use wrapper objects and modifying original class):
def printPrice(price: Price) = price match {
case p: Price if (p.value <= 9000) => println("price is " + p.value)
case p: Price => println("price is over 9000")
}
PS: credits to flavian for showing that you can use if in pattern. Upvoting your answer for this.
You could get away with a PIMP my library pattern:
case class RichPrice(value: Int) {}
implicit def priceToRichPrice(price: Price): RichPrice = RichPrice(price.value)
def printPrice(x: RichPrice): Unit = {
x match {
case RichPrice(value) if (value <= 9000) => println("below 9000")
case RichPrice(value) if (value > 9000) => println("over 9000")
case _ => println("wtf")
}
}
println(printPrice(new Price(10)))
println(printPrice(new Price(9001)))
The point of using a case class is to let Scala define the apply method and unapply magic used for pattern matching.