diff --git a/build.sbt b/build.sbt index ad459d6..3cbf033 100644 --- a/build.sbt +++ b/build.sbt @@ -2,4 +2,6 @@ name := "Unit Testing in Scala" version := "0.1" -scalaVersion := "2.13.1" \ No newline at end of file +scalaVersion := "2.13.1" + +libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.0" % "test" \ No newline at end of file diff --git a/src/main/scala/com/h2/diagrams_and_screenshots/AssertVariants.png b/src/main/scala/com/h2/diagrams_and_screenshots/AssertVariants.png new file mode 100644 index 0000000..44015c6 Binary files /dev/null and b/src/main/scala/com/h2/diagrams_and_screenshots/AssertVariants.png differ diff --git a/src/main/scala/com/h2/diagrams_and_screenshots/ScalaTest_lifecycle.png b/src/main/scala/com/h2/diagrams_and_screenshots/ScalaTest_lifecycle.png new file mode 100644 index 0000000..720bb2d Binary files /dev/null and b/src/main/scala/com/h2/diagrams_and_screenshots/ScalaTest_lifecycle.png differ diff --git a/src/main/scala/com/h2/entities/Dollars.scala b/src/main/scala/com/h2/entities/Dollars.scala index c5cf659..e8b391c 100644 --- a/src/main/scala/com/h2/entities/Dollars.scala +++ b/src/main/scala/com/h2/entities/Dollars.scala @@ -5,7 +5,7 @@ object Dollars { def apply(a: Int): Dollars = new Dollars(a) } -class Dollars(val amount: Int) extends AnyVal with Ordered[Dollars] { +class Dollars(val amount: Int) extends AnyVal with Ordered[Dollars] { override def compare(that: Dollars): Int = amount - that.amount def +(dollars: Dollars): Dollars = new Dollars(amount + dollars.amount) diff --git a/src/main/scala/com/h2/services/CustomerService.scala b/src/main/scala/com/h2/services/CustomerService.scala index e61d50c..a3b17bc 100644 --- a/src/main/scala/com/h2/services/CustomerService.scala +++ b/src/main/scala/com/h2/services/CustomerService.scala @@ -23,6 +23,9 @@ trait CustomerService extends CustomersDb { LocalDate.of(year.toInt, month.toInt, day.toInt) } + val existingCustomer: Option[Customer] = getExistingCustomer(email) + if (existingCustomer.isDefined) return existingCustomer.get.id + val customer = new Customer(first, last, Email(email), getDateOfBirth) saveCustomer(customer) customer.id diff --git a/src/main/scala/com/h2/services/Db.scala b/src/main/scala/com/h2/services/Db.scala index d0d9da4..73baf04 100644 --- a/src/main/scala/com/h2/services/Db.scala +++ b/src/main/scala/com/h2/services/Db.scala @@ -9,6 +9,9 @@ trait CustomersDb { def saveCustomer(customer: Customer): Unit = customers += (customer.id -> customer) def getCustomer(id: UUID): Option[Customer] = customers.get(id) def numCustomers: Int = customers.size + def getExistingCustomer(email: String): Option[Customer] = { + customers.values.filter(c => c.email.toString == email).lastOption + } } trait ProductsDb { diff --git a/src/main/scala/steps.txt b/src/main/scala/steps.txt new file mode 100644 index 0000000..fca071b --- /dev/null +++ b/src/main/scala/steps.txt @@ -0,0 +1,10 @@ +1) We made sure the project built using 'sbt "runMain BankOfScala"' + +2) Add 'libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.0" % "test"' to dependencies + +3) create src/test/scala/*class*-test.scala + +4) - Run tests in Intellij: Add config to run test; click add conf, click '+', scan for Scala test, name the config 'Tests', run all in package, select 'across all modules', use sbt, use "Unit-Testing-In-Scala" module, apply then ok, click play button + - Run tests in terminal: 'sbt', then 'testOnly *DollarSpec'. This is to test the suite (all tests within) 'DollarSpec'. + - Run all tests in terminal: 'sbt', 'test' + diff --git a/src/test/scala/HelloWorldFun.scala b/src/test/scala/HelloWorldFun.scala new file mode 100644 index 0000000..6f52482 --- /dev/null +++ b/src/test/scala/HelloWorldFun.scala @@ -0,0 +1,7 @@ +import org.scalatest.funsuite.AnyFunSuite + +class HelloWorldFun extends AnyFunSuite{ + test("A String 'Hello World' should start with 'Hello'"){ + assert("Hello World".startsWith("Hello")) + } +} diff --git a/src/test/scala/HelloWorldSpec.scala b/src/test/scala/HelloWorldSpec.scala new file mode 100644 index 0000000..17b234e --- /dev/null +++ b/src/test/scala/HelloWorldSpec.scala @@ -0,0 +1,14 @@ +import org.scalatest.flatspec.AnyFlatSpec + +class HelloWorldSpec extends AnyFlatSpec{ + + // FlatSpec style - the specification text and test are in a flat structure no nesting + behavior of "Hello World" + + it should "start with 'Hello'" in { + assert("Hello World".startsWith("Hello")) + } + it should "end with 'World'" in { + assert("Hello World".endsWith("World")) + } +} \ No newline at end of file diff --git a/src/test/scala/assertions/DollarSpec.scala b/src/test/scala/assertions/DollarSpec.scala new file mode 100644 index 0000000..25684c0 --- /dev/null +++ b/src/test/scala/assertions/DollarSpec.scala @@ -0,0 +1,73 @@ +package assertions + +import com.h2.entities.Dollars +import org.scalatest.flatspec.AnyFlatSpec + +class DollarSpec extends AnyFlatSpec{ + + behavior of "A Dollar" + + it should "create a Dollar object for number 10 as input" in { + val tenDollars = Dollars(10) + assert("$10" === tenDollars.toString) + } + + it should "correctly identify that $10 > $5" in { + val tenDollars = Dollars(10) + val fiveDollars = Dollars(5) + // we extends the Ordered[Dollars] trait in 'Dollar' class and implemented the 'compare' method + // this allowed us to use the comparison symbols '<' and '>' + assert(tenDollars > fiveDollars) + } + + it should "correctly identify that $2 < $10" in { + val tenDollars = Dollars(10) + val twoDollars = Dollars(2) + // we extends the Ordered[Dollars] trait in 'Dollar' class and implemented the 'compare' method + // this allowed us to use the comparison symbols '<' and '>' + assert(twoDollars < tenDollars) + } + + it should "correctly add two Dollar amounts" in { + val tenDollars = Dollars(10) + val twoDollars = Dollars(2) + assertResult("$12"){ + (twoDollars + tenDollars).toString + } + } + + it should "correctly subtract two Dollar amounts" in { + val tenDollars = Dollars(10) + val twoDollars = Dollars(2) + assertResult("$8"){ + (tenDollars - twoDollars).toString + } + } + + it should "correctly identify that $4 == $4" in { + val fourDollars = Dollars(4) + assertResult(true){ + fourDollars === fourDollars + } + } + + it should "throw an exception when an invalid integer is provided to create Dollars" in { + // the type of error expected in [] + assertThrows[ArithmeticException]{ + // the input you expect to cause an exception to be thrown + Dollars(10/0) + } + } + + it should "have every dollar more than 0" in { + // assume api call + val dollars: List[Dollars] = List.empty + + // PreConditions: The test would pass without this line. We want to cancel it as there are no dollars. + assume(dollars.nonEmpty) + + dollars.foreach{ d => + assert(d.amount > 0) + } + } +} diff --git a/src/test/scala/assertions/EmailSpec.scala b/src/test/scala/assertions/EmailSpec.scala new file mode 100644 index 0000000..f9b8855 --- /dev/null +++ b/src/test/scala/assertions/EmailSpec.scala @@ -0,0 +1,58 @@ +package assertions + +import com.h2.entities.Email +import org.scalatest.flatspec.AnyFlatSpec + +class EmailSpec extends AnyFlatSpec{ + + behavior of "An Email" + + it should "return an Email object for a valid string" in { + val email = Email("howdy@google.com") + + // you can add a 'clue' as a second arg to 'assert' + assert(email.localPart === "howdy", "expected localPart to be 'howdy'") + assert(email.domain === "google.com") + } + + it should "return another Email object for another valid String" in { + assertResult("jim"){ + Email("jim@google.com").localPart + } + // if a test is not complete, leave this for colleague. + // also means you can tests/build tests one at at time + // fail("Test all properties of Email object") + } + + // if you want a test to be ignored, replace 'it' with 'ignore' + it should "throw an exception when an email does not contain the '@' symbol" in { + withClue("expected IllegalArgumentException since email does not have '@' symbol"){ + assertThrows[IllegalArgumentException] { + Email("jim.com") + } + } + } + it should "throw an exception when an email contains more than one '@' symbol" in { + assertThrows[IllegalArgumentException]{ + Email("jim@@gmail.com") + } + } + + it should "intercept the correct error message when email contains no '@' symbol" in { + val exception = intercept[IllegalArgumentException]{ + Email("jim.com") + } + assert(exception.isInstanceOf[IllegalArgumentException]) + assert(exception.getMessage.contains("does not contain '@'")) + } + + it should "intercept the correct error message when email contains more than one '@' symbol" in { + val exception = intercept[IllegalArgumentException]{ + Email("jim@larry@email.com") + } + assert(exception.isInstanceOf[IllegalArgumentException]) + assert(exception.getMessage.contains("should not contain '@'")) + } + + +} diff --git a/src/test/scala/matchers/ContainerSpec.scala b/src/test/scala/matchers/ContainerSpec.scala new file mode 100644 index 0000000..54556a0 --- /dev/null +++ b/src/test/scala/matchers/ContainerSpec.scala @@ -0,0 +1,91 @@ +package matchers + +import com.h2.services.Currency + +class ContainerSpec extends UnitSpec { + + behavior of "Currencies in a wallet" + + it should "contain a currency that is added to a List wallet" in { + val oneUsd: Currency = "1 USD" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + + val wallet = List(oneUsd, twoEuros, tenCad) + + wallet should contain (oneUsd) + } + + // NEGATIVE CONDITIONAL + it should "not contain a currency that is not added to a List wallet" in { + val oneUsd: Currency = "1 USD" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + val tenNzd: Currency = "10 NZD" + + val wallet = List(oneUsd, twoEuros, tenCad) + + wallet should not contain (tenNzd) + } + + it should "contain a currency that is added to a Set wallet" in { + val oneUsd: Currency = "1 USD" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + + val wallet = Set(oneUsd, twoEuros, tenCad) + + wallet should contain (oneUsd) + } + + // NEGATIVE CONDITIONAL + it should "not contain a currency that is not added to a Set wallet" in { + val oneUsd: Currency = "1 USD" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + val tenNzd: Currency = "10 NZD" + + val wallet = Set(oneUsd, twoEuros, tenCad) + + wallet should not contain (tenNzd) + } + + it should "contain a currency that is added to a Map wallet" in { + val oneUsd: Currency = "1 USD" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + + val wallet: Map[String, Currency] = Map("USD" -> oneUsd, "EUR" -> twoEuros, "CAD" -> tenCad) + + wallet should contain ("USD" -> oneUsd) + } + + it should "contain a oneOf 1 USD that is added to a Set wallet" in { + val oneUsd: Currency = "1 USD" + val anotherOneUsd: Currency = "1 USD" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + val hundredCad: Currency = "100 CAD" + + val wallet = Set(oneUsd, anotherOneUsd, twoEuros, tenCad) + + // collection should contain exactly 'oneOf' oneUsd OR hundredCad + wallet should contain oneOf (oneUsd, hundredCad) + // for lists this must be 'oneElementOf' instead + wallet should contain oneElementOf List(oneUsd, hundredCad) + } + + it should "contain noneOf 1 USD and 100 INR that is added to a Set wallet" in { + val oneUsd: Currency = "1 USD" + val hundredInr: Currency = "100 INR" + val twoEuros: Currency = "2 EUR" + val tenCad: Currency = "10 CAD" + val hundredCad: Currency = "100 CAD" + + val wallet: Set[Currency] = Set(twoEuros, twoEuros, tenCad, hundredCad) + + wallet should contain noneOf(oneUsd, hundredInr) + // Same thing for Lists + wallet should contain noElementsOf List(oneUsd, hundredInr) + } +} diff --git a/src/test/scala/matchers/EmptinessSpec.scala b/src/test/scala/matchers/EmptinessSpec.scala new file mode 100644 index 0000000..8d48e4f --- /dev/null +++ b/src/test/scala/matchers/EmptinessSpec.scala @@ -0,0 +1,39 @@ +package matchers + +import com.h2.services.{Currency, CustomerService} + +class EmptinessSpec extends UnitSpec { + val customerService: CustomerService = new CustomerService {} + behavior of "Customer for emptiness" + + it should "return empty for customer's last name" in { + val (first, last, email, dateOfBirth) = ("Shane", "", "shane@google.com", "1983/02/01") + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.last should be (empty) + // alternative syntax + customer.last shouldBe empty + + // NEGATIVE CONDITION + customerId.toString should not be (empty) + } + + behavior of "Currencies inside wallet" + + it should "be empty when no currencies are added to wallet" in { + + val wallet: List[Currency] = List.empty + + wallet should be(empty) + } + + // NEGATIVE CONDITION + it should "not be empty when currencies are added to wallet" in { + + val wallet: List[Currency] = List("1 USD") + + wallet should not be(empty) + } + +} diff --git a/src/test/scala/matchers/EqualitySpec.scala b/src/test/scala/matchers/EqualitySpec.scala new file mode 100644 index 0000000..bc7ba0f --- /dev/null +++ b/src/test/scala/matchers/EqualitySpec.scala @@ -0,0 +1,70 @@ +package matchers + +import com.h2.services.Currency +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +// TESTING EQUALITY +// note: works with all except arrays as arrays compare whether its the same instance + +// uses our BaseTest +class EqualitySpec extends UnitSpec { + + behavior of "Currency Equals" + it should "match two 10 USD currencies as equal when using 'should equal' syntax" in { + val currency1: Currency = "10 USD" + val currency2: Currency = "10 USD" + + currency1 should equal(currency2) + } + + behavior of "Currency Equals" + it should "match two 10 USD currencies as equal when using 'should ===' syntax" in { + val currency1: Currency = "10 USD" + val currency2: Currency = "10 USD" + + currency1 should === (currency2) + } + + behavior of "Currency Equals" + it should "match two 10 USD currencies as equal when using 'shouldEqual' syntax" in { + val currency1: Currency = "10 USD" + val currency2: Currency = "10 USD" + + currency1 shouldEqual currency2 + } + + behavior of "Currency Equals" + it should "match two 10 USD currencies as equal when using 'shouldBe' syntax" in { + val currency1: Currency = "10 USD" + val currency2: Currency = "10 USD" + + currency1 shouldBe currency2 + } + + behavior of "Currency Equals" + it should "match two 10 USD currencies as equal when using 'should be' syntax" in { + val currency1: Currency = "10 USD" + val currency2: Currency = "10 USD" + + currency1 should be (currency2) + } + + // NEGATIVE CONDITIONS + behavior of "Currency Equals" + it should "not match 10 USD and 100 USD currencies as equal when using 'should be' syntax" in { + val tenUsd: Currency = "10 USD" + val hundredUsd: Currency = "100 USD" + + tenUsd should not be (hundredUsd) + } + + behavior of "Currency Equals" + it should "not match 10 USD and 100 USD currencies as equal when using 'should equal' syntax" in { + val tenUsd: Currency = "10 USD" + val hundredUsd: Currency = "100 USD" + + tenUsd should not equal (hundredUsd) + } + +} diff --git a/src/test/scala/matchers/ExceptionSpec.scala b/src/test/scala/matchers/ExceptionSpec.scala new file mode 100644 index 0000000..7a83ac6 --- /dev/null +++ b/src/test/scala/matchers/ExceptionSpec.scala @@ -0,0 +1,17 @@ +package matchers + +import com.h2.services.Currency + +class ExceptionSpec extends UnitSpec { + + behavior of "Currency during the exception" + + it should "throw an exception when invalid number is provided in the current string" in { + a [NumberFormatException] should be thrownBy Currency.stringToCurrency("Two USD") + } + + it should "provide a detailed description of exception" in { + val exception = the [NumberFormatException] thrownBy Currency.stringToCurrency("Two USD") + exception.getMessage should include("Two") + } +} diff --git a/src/test/scala/matchers/LengthAndSizeSpec.scala b/src/test/scala/matchers/LengthAndSizeSpec.scala new file mode 100644 index 0000000..69bb7fd --- /dev/null +++ b/src/test/scala/matchers/LengthAndSizeSpec.scala @@ -0,0 +1,38 @@ +package matchers + +import com.h2.services.CustomerService + +import java.util.UUID + +class LengthAndSizeSpec extends UnitSpec { + val customerService: CustomerService = new CustomerService {} + + behavior of "CustomerService for length" + + it should "return correct length for customer's first and last name" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.first should have length first.length + customer.last should have length last.length + } + + behavior of "CustomerService for size" + + it should "return correct size for number of customers created" in { + val newCustomers: Seq[(String, String, String, String)] = List ( + ("John", "Smith", "john@smith.com", "1982/12/31"), + ("Amy", "Grove", "amy@grove.com", "1989/07/10") + ) + + val customerIds: Seq[UUID] = newCustomers.map(newCustomer => + customerService.createNewCustomer(newCustomer._1, newCustomer._2, newCustomer._3, newCustomer._4)) + + customerIds should have size 2 + } + + + +} diff --git a/src/test/scala/matchers/LogicalSpec.scala b/src/test/scala/matchers/LogicalSpec.scala new file mode 100644 index 0000000..4ec3d0e --- /dev/null +++ b/src/test/scala/matchers/LogicalSpec.scala @@ -0,0 +1,23 @@ +package matchers + +import com.h2.services.Currency + +// COMBINE LOGICAL OPERATORS FOR MULTIPLE BOOLEANS + +class LogicalSpec extends UnitSpec { + + behavior of "Currencies as logical and/or" + + it should "successfully match logical expression with 'and' condition for a currency" in { + val tenNzd: Currency = "10 NZD" + + tenNzd.costInDollars.amount should (be > (0) and be <= (10)) + } + + it should "successfully match logical expression with 'OR' condition for a currency" in { + val tenNzd: Currency = "10 NZD" + + tenNzd.code should (have length (0) or equal ("NZD")) + } + +} diff --git a/src/test/scala/matchers/ObjectIdentitySpec.scala b/src/test/scala/matchers/ObjectIdentitySpec.scala new file mode 100644 index 0000000..4f37ded --- /dev/null +++ b/src/test/scala/matchers/ObjectIdentitySpec.scala @@ -0,0 +1,24 @@ +package matchers + +import com.h2.entities.Customer +import com.h2.services.CustomerService + +// COMPARING OBJECT INSTANCES +class ObjectIdentitySpec extends UnitSpec { + val customerService: CustomerService = new CustomerService {} + + behavior of "CustomerService when creating new customers" + + it should "create one customer for a given email address" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + val customerId1 = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customerId2 = customerService.createNewCustomer(first, last, email, dateOfBirth) + + val customer1 = customerService.getCustomer(customerId1).get + val customer2 = customerService.getCustomer(customerId2).get + + // We added 'getExistingCustomer()' to 'db' class. + // The used that in 'CustomerService' class to prevent duplicate emails/customers in system + customer1 should be theSameInstanceAs customer2 + } +} diff --git a/src/test/scala/matchers/OrderingSpec.scala b/src/test/scala/matchers/OrderingSpec.scala new file mode 100644 index 0000000..936e636 --- /dev/null +++ b/src/test/scala/matchers/OrderingSpec.scala @@ -0,0 +1,38 @@ +package matchers + +import com.h2.services.Currency +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +// USING GREATER THAN + +class OrderingSpec extends AnyFlatSpec with Matchers { + + behavior of "Currency Conversion Cost in Comparison" + + it should "report equal costs for 10 USD, 10 USD" in { + val tenUsd: Currency = "10 USD" + val anotherTenUsd: Currency = "10 USD" + + tenUsd.costInDollars.amount should be >= anotherTenUsd.costInDollars.amount + } + + it should "report higher costs for 100 USD, 10 USD" in { + val tenUsd: Currency = "10 USD" + val hundredUsd: Currency = "100 USD" + + hundredUsd.costInDollars.amount should be > tenUsd.costInDollars.amount + } + + it should "report 1 USD < 10 USD" in { + val tenUsd: Currency = "10 USD" + val oneUsd: Currency = "1 USD" + + oneUsd.costInDollars.amount should be < tenUsd.costInDollars.amount + } + + // Work lexicographically too + it should "report NZD < USD" in { + "NZD" should be < "USD" + } +} diff --git a/src/test/scala/matchers/StringSpec.scala b/src/test/scala/matchers/StringSpec.scala new file mode 100644 index 0000000..6c27065 --- /dev/null +++ b/src/test/scala/matchers/StringSpec.scala @@ -0,0 +1,60 @@ +package matchers + +import com.h2.services.CustomerService +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +// BASIC STRING COMPARISONS +// note: the underlying .equals method is used + +class StringSpec extends AnyFlatSpec with Matchers{ + + val customerService: CustomerService = new CustomerService {} + behavior of "Customer Service for Strings" + + it should "correctly match the customer email starting with first name" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.email.toString should startWith(first.toLowerCase) + } + + it should "correctly match the customer email ending with '.com'" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.email.toString should endWith(".com") + } + + it should "correctly match the customer email contains '@' symbol" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.email.toString should include("@") + } + + it should "correctly match the customer email as regular expression" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.email.toString should include regex "[a-z]+[@.]com" + } + + it should "correctly match the customer dateOfBirth as fullyMatch regular expression" in { + val (first, last, email, dateOfBirth) = ("John", "Smith", "john@smith.com", "1982/12/31") + + val customerId = customerService.createNewCustomer(first, last, email, dateOfBirth) + val customer = customerService.getCustomer(customerId).get + + customer.dateOfBirth.toString should fullyMatch regex """[0-9]{4}-[0-9]{2}-[0-9]{2}""" + } + +} diff --git a/src/test/scala/matchers/UnitSpec.scala b/src/test/scala/matchers/UnitSpec.scala new file mode 100644 index 0000000..1a0754d --- /dev/null +++ b/src/test/scala/matchers/UnitSpec.scala @@ -0,0 +1,9 @@ +package matchers + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +// CREATING A BASE TEST - +// TO AVOID REPEATING THE ANY-FLATSPEC WITH MATCHERS IN ALL TESTS + +abstract class UnitSpec extends AnyFlatSpec with Matchers