Search code examples
pattern-matchingscala-collectionsscalatest

How to write a ScalaTest for a pattern matching method that uses a Map collection


I'm trying to write my first set of ScalaTest tests for the following Scala Object that converts time in 24 hour format (00:00 to 24:00) into time as words (Eight o'clock before midday, Half past eight before midday, Quarter to nine before midday). I'm looking for a starting point in how to write tests for hourFmt, minutesFmt and Fmt that use pattern matching and a Map collection to return the appropriate word for an integer value for hours and minutes.

I have read most of ScalaTest User Guide Documentation, but wasn't able to find any documentation on Pattern Matching Methods or Map Collections. I'm just looking for a starting point or examples that the community is aware of as the following will be my first time writing unit tests.

I've attached my code below to clarify the three methods that I've spoken about.

object TimeAsWords extends App {
  def parseTime(hhmm: String): (Int, Int) = {
    """^([01]\d|2[0-4]):([0-5]\d)$""".r.findAllIn(hhmm).matchData foreach {
      md => return (md.group(1).toInt, md.group(2).toInt)
    }
    throw new IllegalArgumentException("Input string doesn't match required format: 00:00 - 24:00")
  }

  def formatTime(hhmm: (Int, Int)): String = {
    val englishNum = Map(
      1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four", 5 -> "five",
      6 -> "six", 7 -> "seven", 8 -> "eight", 9 -> "nine", 10 -> "ten",
      11 -> "eleven", 12 -> "twelve", 13 -> "thirteen", 14 -> "fourteen",
      16 -> "sixteen", 17 -> "seventeen", 18 -> "eighteen", 19 -> "nineteen",
      20 -> "twenty", 21 -> "twenty-one", 22 -> "twenty-two",
      23 -> "twenty-three", 24 -> "twenty-four", 25 -> "twenty-five",
      26 -> "twenty-six", 27 -> "twenty-seven", 28 -> "twenty-eight",
      29 -> "twenty-nine"
    )

    def hourFmt(h: Int): String = h match {
      case 0 | 24      => "midnight"
      case 12          => "noon"
      case _ if h < 12 => englishNum(h) + " o'clock before midday"
      case _           => englishNum(h - 12) + " o'clock after midday"
    }

    def minuteFmt(m: Int): String = "%s %s".format(
      englishNum(m),
      if (m == 1) "minute" else "minutes"
    )

    def fmt(m: Int): (Int => String) = m match {
      case  0          => hourFmt
      case 15          => "quarter past " + hourFmt(_)
      case 30          => "half past " + hourFmt(_)
      case 45          => h => "quarter to " + hourFmt(h + 1)
      case _ if m < 30 => h => "%s past %s".format(minuteFmt(m), hourFmt(h))
      case _           => h => "%s to %s".format(minuteFmt(60 - m), hourFmt(h + 1))
    }

    val (hh, mm) = hhmm
    fmt(mm)(hh).capitalize
  }

  try {
    println(formatTime(parseTime(args(0))))
  } catch {
    case e: IllegalArgumentException => System.err.println(e.getMessage)
      System.exit(1)
  }
}

Solution

  • I'm posting the answer to my own question, but would like to find a better way of writing unit tests for a function fmt that uses pattern matching that's reliant on two other functions hourFmt and minutesFmt.

    Current ScalaTest that I wrote:

    package time
    
    import TimeAsWords._
    import org.scalatest.FunSuite
    
    /**
      * Created by PeterW on 6/18/2017.
      */
    class TimeAsWordsTest extends FunSuite {
      test("testParseTime - 00:00") {
        assert(parseTime("00:00") == (0,0))
      }
      test("testParseTime - IllegalArgumentException") {
        val thrown = intercept[Exception] {
          parseTime("25:00")
        }
        assert(thrown.getMessage == "Input string doesn't match required format: 00:00 - 24:00")
      }
      test("testFormatTime - Midnight") {
        assert(formatTime((0,0)) == "Midnight")
      }
      test("testFormatTime - Noon") {
        assert(formatTime((12,0)) == "Noon")
      }
      test("testFormatTime - o'clock before midday") {
        assert(formatTime((8,0)) == "Eight o'clock before midday")
      }
      test("testFormatTime - o'clock after midday") {
        assert(formatTime((13,0)) == "One o'clock after midday")
      }
      test("testFormatTime - Quater past") {
        assert(formatTime((13,15)) == "Quarter past one after midday")
      }
      test("testFormatTime - Half past") {
        assert(formatTime((13,30)) == "Half past one after midday")
      }
      test("testFormatTime - Quarter to") {
        assert(formatTime((13,45)) == "Quarter to two after midday")
      }
      test("testFormatTime - Minutes past hour") {
        assert(formatTime((13,25)) == "Twenty-five minutes past one after midday")
      }
      test("testFormatTime - Minutes to hour") {
        assert(formatTime((13,35)) == "Twenty-five minutes to two after midday")
      }
      test("testFormatTime - Minute singular") {
        assert(formatTime((13,1)) == "One minute past one after midday")
      }
      test("testFormatTime - Minutes plural") {
        assert(formatTime((13,2)) == "Two minutes past one after midday")
      }
    }