org.specs2.matcher.Matcher Scala Examples

The following examples show how to use org.specs2.matcher.Matcher. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.
Example 1
Source File: AnnotatedDepNodeTransformerTest.scala    From exodus   with MIT License 5 votes vote down vote up
package com.wixpress.build.bazel

import com.wixpress.build.maven.MavenMakers._
import com.wixpress.build.maven.{BazelDependencyNode, Coordinates}
import org.specs2.matcher.{AlwaysMatcher, Matcher}
import org.specs2.mutable.SpecificationWithJUnit

//noinspection TypeAnnotation
class AnnotatedDepNodeTransformerTest extends SpecificationWithJUnit {

  "AnnotatedDependencyNodeTransformer" should {
    "return import external rule with 'linkable' transitive runtime dep if came from global overrided list and is missing from local neverlink " in {
      val transformer = new AnnotatedDependencyNodeTransformer(new NeverLinkResolver(transitiveDeps, localNeverlinkDependencies = Set()))

      transformer.annotate(runtimeDepNode) must beAnnotatedDependencyNode(
        anArtifact = be_===(artifact),
        runtimeDeps = contain(ImportExternalDep(transitiveDep, linkableSuffixNeeded = true)),
        compileDeps = beEmpty)
    }

    "return import external rule with 'linkable' transitive compile dep if came from global overrided list and is missing from local neverlink " in {
      val transformer = new AnnotatedDependencyNodeTransformer(new NeverLinkResolver(transitiveDeps, localNeverlinkDependencies = Set()))

      transformer.annotate(compileTimeDepNode) must beAnnotatedDependencyNode(
        anArtifact = be_===(artifact),
        runtimeDeps = beEmpty,
        compileDeps = contain(ImportExternalDep(transitiveDep, linkableSuffixNeeded = true))
      )
    }

    "return import external rule with transitive runtime dep without 'linkable' suffix if it can be found both on global and local neverlink lists" in {
      val transformer = new AnnotatedDependencyNodeTransformer(new NeverLinkResolver(transitiveDeps, localNeverlinkDependencies = transitiveDeps))

      transformer.annotate(runtimeDepNode) must beAnnotatedDependencyNode(
        anArtifact = be_===(artifact),
        runtimeDeps = contain(ImportExternalDep(transitiveDep, linkableSuffixNeeded = false)),
        compileDeps = beEmpty
      )
    }

    "return import external rule with transitive compiletime dep without 'linkable' suffix if it can be found both on global and local neverlink lists" in {
      val transformer = new AnnotatedDependencyNodeTransformer(new NeverLinkResolver(transitiveDeps, localNeverlinkDependencies = transitiveDeps))

      transformer.annotate(compileTimeDepNode) must beAnnotatedDependencyNode(
        anArtifact = be_===(artifact),
        runtimeDeps = beEmpty,
        compileDeps = contain(ImportExternalDep(transitiveDep, linkableSuffixNeeded = false))
      )
    }
  }

  val artifact = someCoordinates("some-artifact")
  val transitiveDep = someCoordinates("some-transitiveDep")
  val transitiveDeps = Set(transitiveDep)
  val runtimeDepNode = BazelDependencyNode(asCompileDependency(artifact),Set(asRuntimeDependency(transitiveDep)))
  val compileTimeDepNode = BazelDependencyNode(asCompileDependency(artifact),Set(asCompileDependency(transitiveDep)))



  def beAnnotatedDependencyNode(anArtifact: Matcher[Coordinates] = AlwaysMatcher[Coordinates](),
                           runtimeDeps: Matcher[Set[BazelDep]] = AlwaysMatcher[Set[BazelDep]](),
                           compileDeps: Matcher[Set[BazelDep]] = AlwaysMatcher[Set[BazelDep]]()
             ): Matcher[AnnotatedDependencyNode] =
    anArtifact ^^ {
      (_: AnnotatedDependencyNode).baseDependency.coordinates aka "artifact"
    } and runtimeDeps ^^ {
      (_: AnnotatedDependencyNode).runtimeDependencies aka "runtimeDeps"
    } and compileDeps ^^ {
      (_: AnnotatedDependencyNode).compileTimeDependencies aka "compileTimeDeps"
    }
} 
Example 2
Source File: AdditionalDepsByMavenDepsOverridesReaderIT.scala    From exodus   with MIT License 5 votes vote down vote up
package com.wix.bazel.migrator.overrides

import java.nio.file.Path

import org.specs2.matcher.Matcher
import org.specs2.mutable.SpecificationWithJUnit
import org.specs2.specification.Scope

//noinspection TypeAnnotation
class AdditionalDepsByMavenDepsOverridesReaderIT extends SpecificationWithJUnit {
  "read" should {
    "throw parse exception given invalid overrides json string" in new Context {
      writeOverrides("invl:")

      AdditionalDepsByMavenDepsOverridesReader.from(overridesPath) must throwA[OverrideParsingException]
    }

    "read overrides from manual json" in new Context {

      writeOverrides(
        s"""{ "overrides" : [{
           |   "groupId" : "$groupId",
           |   "artifactId" : "$artifactId",
           |   "additionalDeps": {
           |      "deps" : ["$dependency"],
           |      "runtimeDeps" : ["$runtimeDependency"]
           |    }
           |  }
           |]}""".stripMargin)
      private val expectedOverride = AdditionalDepsByMavenDepsOverride(
        groupId,
        artifactId,
        AdditionalDeps(
          deps = Set(dependency),
          runtimeDeps = Set(runtimeDependency)))
      AdditionalDepsByMavenDepsOverridesReader.from(overridesPath) must containExactly(expectedOverride)
    }

    "read overrides from manual json with only runtime deps" in new Context {
      writeOverrides(
        s"""{ "overrides" : [{
           |   "groupId" : "$groupId",
           |   "artifactId" : "$artifactId",
           |   "additionalDeps": {
           |      "runtimeDeps" : ["$runtimeDependency"]
           |    }
           |  }
           |]}""".stripMargin)
      private val expectedOverride = AdditionalDepsByMavenDepsOverride(
        groupId,
        artifactId,
        AdditionalDeps(
          deps = Set.empty,
          runtimeDeps = Set(runtimeDependency)))
      AdditionalDepsByMavenDepsOverridesReader.from(overridesPath) must containExactly(expectedOverride)
    }.pendingUntilFixed("currently it reads 'deps' field as null, must specify empty array")

    "read overrides from generated json" in new Context {
      val overrides = AdditionalDepsByMavenDepsOverrides(List(AdditionalDepsByMavenDepsOverride(
        groupId,
        artifactId,
        AdditionalDeps(
          deps = Set(dependency),
          runtimeDeps = Set(runtimeDependency)))))
      writeOverrides(objectMapper.writeValueAsString(overrides))

      AdditionalDepsByMavenDepsOverridesReader.from(overridesPath) must beEqualTo(overrides)
    }

    "default to no overrides when trying to read an non existent overrides file" in new Context {
      AdditionalDepsByMavenDepsOverridesReader.from(overridesPath) must haveNoOverrides
    }

  }

  abstract class Context extends Scope with OverridesReaderITSupport {
    val groupId = "some.group"
    val artifactId = "some-artifact"
    val dependency = "//some:dependency"
    val runtimeDependency = "//some/runtime:dependency"
    override val overridesPath: Path = setupOverridesPath(repoRoot, "additional_deps_by_maven.overrides")
  }

  def containExactly(expectedOverride:AdditionalDepsByMavenDepsOverride): Matcher[AdditionalDepsByMavenDepsOverrides] =
    {(_:AdditionalDepsByMavenDepsOverrides).overrides} ^^ contain(exactly(expectedOverride))

  def haveNoOverrides: Matcher[AdditionalDepsByMavenDepsOverrides] =
    {(_:AdditionalDepsByMavenDepsOverrides).overrides} ^^ beEmpty


} 
Example 3
Source File: BazelRcWriterIT.scala    From exodus   with MIT License 5 votes vote down vote up
package com.wix.bazel.migrator

import java.nio.file.Path

import org.specs2.matcher.Matcher

class BazelRcWriterIT extends BaseWriterIT {
  "BazelRcWriter" should {
    "create file with the expected content" in new ctx {
      bazelRcWriter.write()

      bazelRcPath must beRegularFile(withContentMatching = ===(expectedBazelRcContent))
    }
  }

  trait ctx extends baseCtx {
    final val bazelRcPath: Path = path(withName = ".bazelrc")
    final val bazelRcWriter: BazelRcWriter = new BazelRcWriter(repoRoot)
    final val expectedBazelRcContent =
      """#
        |# DO NOT EDIT - this line imports shared managed bazel configuration
        |#
        |import %workspace%/tools/bazelrc/.bazelrc.managed.dev.env
        |
        |#
        |# ADDITIONS ONLY UNDER THIS LINE
        |#
        |
      """.stripMargin
  }

  def contentContainsLine(line: String): Matcher[String] = contentContainsLines(List(line))

  def contentContainsLines(lines: List[String]): Matcher[String] = {(_:String).split(System.lineSeparator()).toList} ^^ containAllOf(lines)

  def contentContainsExactlyLines(lines: List[String]): Matcher[String] = {(_:String).split(System.lineSeparator()).toList} ^^ containTheSameElementsAs(lines)

} 
Example 4
Source File: DockerImagesWriterTest.scala    From exodus   with MIT License 5 votes vote down vote up
package com.wix.bazel.migrator

import java.nio.file.{Files, Path}

import better.files.File
import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder
import com.wix.bazel.migrator.overrides.{InternalTargetOverride, InternalTargetsOverrides}
import org.specs2.matcher.{Matcher, Scope}
import org.specs2.mutable.SpecificationWithJUnit

class DockerImagesWriterTest extends SpecificationWithJUnit {
  abstract class ctx extends Scope{

    def containExactlyOnce(substr: String): Matcher[String] = {
      {a:String => a.indexOf(substr) must not be_== -1} and {a:String => a.indexOf(substr) must beEqualTo(a.lastIndexOf(substr))}
    }

    val rootfs: Path = MemoryFileSystemBuilder.newLinux().build().getPath("repo-root")
    val overrideWithDockerImages = InternalTargetOverride("some-label", dockerImagesDeps = Option(List("mysql:5.7", "docker-repo.wixpress.com/com.wixpress.whatever/whatever:1.234.5")))
    def overrides: Set[InternalTargetOverride]
    def writer = new DockerImagesWriter(rootfs, InternalTargetsOverrides(overrides))
  }

  "DockerImagesWriter" should {
    "create docker_images.bzl in third_party/docker_images" in new ctx {
      def overrides: Set[InternalTargetOverride] = Set.empty
      writer.write()
      Files.exists(rootfs.resolve("third_party/docker_images/docker_images.bzl")) should beTrue
    }

    "create BUILD.bazel file in third_party/docker_images" in new ctx {
      def overrides: Set[InternalTargetOverride] = Set.empty
      writer.write()
      Files.exists(rootfs.resolve("third_party/docker_images/BUILD.bazel")) should beTrue
    }

    "fill default values in container_pull for short-form image" in new ctx {
      def overrides: Set[InternalTargetOverride] = Set(overrideWithDockerImages)
      writer.write()
      val expected: String =
        s"""|  container_pull(
            |    name = "mysql_5.7",
            |    registry = "index.docker.io",
            |    repository = "library/mysql",
            |    tag = "5.7"
            |  )""".stripMargin
      File(rootfs.resolve("third_party/docker_images/docker_images.bzl")).contentAsString must contain(expected)
    }

    "write values as-is in container_pull for full form image" in new ctx {
      def overrides: Set[InternalTargetOverride] = Set(overrideWithDockerImages)
      writer.write()
      val expected: String =
        s"""|  container_pull(
            |    name = "com.wixpress.whatever_whatever_1.234.5",
            |    registry = "docker-repo.wixpress.com",
            |    repository = "com.wixpress.whatever/whatever",
            |    tag = "1.234.5"
            |  )""".stripMargin
      File(rootfs.resolve("third_party/docker_images/docker_images.bzl")).contentAsString must contain(expected)
    }

    "write container_image in BUILD file" in new ctx {
      def overrides: Set[InternalTargetOverride] = Set(overrideWithDockerImages)
      writer.write()
      val expected: String =
        s"""container_image(name="com.wixpress.whatever_whatever_1.234.5", base="@com.wixpress.whatever_whatever_1.234.5//image")""".stripMargin

      File(rootfs.resolve("third_party/docker_images/BUILD.bazel")).contentAsString must contain(expected)
    }

    "deduplicate images in BUILD file" in new ctx {
      def overrides = Set(overrideWithDockerImages, overrideWithDockerImages.copy(label = "duplicate"))
      writer.write()
      private val fileContent: String = File(rootfs.resolve("third_party/docker_images/BUILD.bazel")).contentAsString
      fileContent must containExactlyOnce("container_image(name=\"mysql_5.7\",")
    }

    "deduplicate images in docker_images.bzl file" in new ctx {

      def overrides = Set(overrideWithDockerImages, overrideWithDockerImages.copy(label = "duplicate"))
      writer.write()
      private val fileContent: String = File(rootfs.resolve("third_party/docker_images/docker_images.bzl")).contentAsString
      fileContent must containExactlyOnce("name = \"mysql_5.7\",")
    }
  }
} 
Example 5
Source File: package.scala    From temperature-machine   with Apache License 2.0 5 votes vote down vote up
package bad.robot.temperature

import cats.effect.IO
import org.http4s.{Header, Response, Status}
import org.specs2.matcher.{Expectable, MatchResult, Matcher}

package object test {

  def haveStatus(status: Status) = new Matcher[Response[IO]] {
    def apply[S <: Response[IO]](e: Expectable[S]): MatchResult[S] = result(
      e.value.status == status,
      s"""Status of [${e.value.status}]
          |
          |is
          |
          |$status""".stripMargin,
      s"""Status of
          |[${e.value.status}]
          |
          |is not
          |
          |[$status]
          |
          |(${e.value.as[String]})""".stripMargin,
      e)
  }

  def containsHeader(name: String, value: String) = new Matcher[Response[IO]] {
    def apply[S <: Response[IO]](e: Expectable[S]): MatchResult[S] = result(
      e.value.headers.toList.map(_.toRaw) contains Header(name, value),
      s"""${e.value.headers}
          |
          |contains
          |
          |$name""".stripMargin,
      s"""The response headers '${e.value.headers.toList.mkString("\n")}'
          |
          |do not contain
          |
          |[$name: $value]
          |""".stripMargin,
      e)
  }
} 
Example 6
Source File: MarshallerTestSupport.scala    From wix-http-testkit   with MIT License 5 votes vote down vote up
package com.wix.e2e.http.drivers

import akka.http.scaladsl.model.{HttpRequest, HttpResponse}
import com.wix.e2e.http.drivers.MarshallingTestObjects.SomeCaseClass
import com.wix.e2e.http.exceptions.MissingMarshallerException
import com.wix.test.random.{randomInt, randomStr}
import org.specs2.execute.AsResult
import org.specs2.matcher.Matcher
import org.specs2.matcher.ResultMatchers.beError

trait MarshallerTestSupport {
  val someObject = SomeCaseClass(randomStr, randomInt)
  val content = randomStr

  def aResponseWith(body: String) = HttpResponse(entity = body)
  def aRequestWith(body: String) = HttpRequest(entity = body)
  val request = HttpRequest()
}


object MarshallingTestObjects {
  case class SomeCaseClass(s: String, i: Int)
}

object MarshallerMatchers {
  def beMissingMarshallerMatcherError[T : AsResult]: Matcher[T] = beError[T](new MissingMarshallerException().getMessage)
} 
Example 7
Source File: PathBuilderTestSupport.scala    From wix-http-testkit   with MIT License 5 votes vote down vote up
package com.wix.e2e.http.client.drivers

import akka.http.scaladsl.model.Uri
import com.wix.e2e.http.BaseUri
import com.wix.test.random.{randomInt, randomStr}
import org.specs2.matcher.Matcher
import org.specs2.matcher.Matchers._


trait PathBuilderTestSupport {
  val contextRoot = s"/$randomStr"
  val contextRootWithMultiplePaths = s"/$randomStr/$randomStr/$randomStr"
  val relativePath = s"/$randomStr"
  val relativePathWithMultipleParts = s"/$randomStr/$randomStr/$randomStr"
  val baseUri = BaseUriGen.random
  val escapedCharacters = "!'();:@+$,/?%#[]\"'/\\" //&=
}

object BaseUriGen {
  def random: BaseUri = BaseUri(randomStr.toLowerCase, randomInt(1, 65536), Some(s"/$randomStr"))
}

object UrlBuilderMatchers {
  def beUrl(url: String): Matcher[Uri] = be_===(url) ^^ { (_: Uri).toString }
} 
Example 8
Source File: AvrohuggerSpec.scala    From avrohugger   with Apache License 2.0 5 votes vote down vote up
package util

import avrohugger._
import avrohugger.format._
import avrohugger.format.abstractions.SourceFormat

import java.io.File
import java.nio.file.{FileSystems, Path}

import org.specs2.SpecificationLike
import org.specs2.matcher.{Matcher, Matchers, ShouldExpectable}

import scala.io.Source

class AvrohuggerSpec(
  inPath: Path,
  val outputFiles: Seq[Path],
  sourceFormat: SourceFormat
) extends Matchers {
  implicit class PathExtensions(
    path: Path
  ) {
    def ++(next: String) = path.resolve(next)
    def ++(other: Path) = path.resolve(other)
  }

  val sourceFormatName = sourceFormat match {
    case SpecificRecord => "specific"
    case Standard => "standard"
    case Scavro => "scavro"
  }

  val gen = new Generator(sourceFormat)
  val inputPath = {
    val sourceBase = FileSystems.getDefault.getPath("avrohugger-core", "src", "test", "avro")
    (sourceBase ++ inPath)
  }
  val inputFile = inputPath.toFile
  val outDir = gen.defaultOutputDir + s"/$sourceFormatName/"

  private def readFile(f: File): String = {
    val source = Source.fromFile(f)
    try source.mkString finally source.close()
  }

  val expectedBase = FileSystems.getDefault.getPath("avrohugger-core", "src", "test", "expected", sourceFormatName)
  val generatedBase = FileSystems.getDefault.getPath("target", "generated-sources", sourceFormatName)

  private def prefixedFileString(prefixPath: Path, p: Path) = {
    val fullPath = sourceFormat match {
      case Scavro => {
        Option(p.getParent) match {
          case Some(parent) => parent ++ "model" ++ p.getFileName
          case None => FileSystems.getDefault.getPath("model") ++ p
        }
      }
      case _ => p
    }
    readFile((prefixPath ++ fullPath).toFile)
  }

  def generatedString(p: Path) = prefixedFileString(generatedBase, p)
  def expectedString(p: Path) = prefixedFileString(expectedBase, p)

  def checkFileToFile = {
    gen.fileToFile(inputFile, outDir)

    val generated = outputFiles map generatedString
    val expected = outputFiles map expectedString

    ShouldExpectable(generated) shouldEqual expected
  }

  def checkFileToStrings = {
    val generated = gen.fileToStrings(inputFile)
    val expected = outputFiles map expectedString

    ShouldExpectable(generated) shouldEqual expected
  }

  def checkStringToFile = {
    val inputString = readFile(inputFile)
    gen.stringToFile(inputString, outDir)

    val generated = outputFiles map generatedString
    val expected = outputFiles map expectedString

    ShouldExpectable(generated) shouldEqual expected
  }

  def checkStringToStrings = {
    val generated = {
      val inputString = readFile(inputFile)
      gen.stringToStrings(inputString)
    }

    val expected = outputFiles map expectedString

    ShouldExpectable(generated) shouldEqual expected
  }
} 
Example 9
Source File: ParsebackSpec.scala    From parseback   with Apache License 2.0 5 votes vote down vote up
package parseback

import cats.Eval

import org.specs2.matcher.Matcher
import org.specs2.mutable._
import org.specs2.specification.SpecificationFeatures

import scala.util.{Left, Right}

trait ParsebackSpec extends Spec with SpecificationFeatures {

  final def recognize[A](input: String, ambiguous: Boolean = true)(implicit W: Whitespace): Matcher[Parser[A]] = { p: Parser[A] =>
    val maybeResults = p(LineStream[Eval](input)).value

    maybeResults match {
      case Right(results) =>
        (ambiguous || results.toList.length == 1,    // TODO implement lengthCompare
          s"recognized '$input'",
          s"recognized '$input', but with multiple results: $results")

      case Left(err) =>
        (false, "", s"failed to recognize '$input' with error $err")
    }
  }

  final def recognizeUnambiguously[A](input: String) = recognize[A](input, false)

  final def parseOk[A](input: String)(results: A*)(implicit W: Whitespace): Matcher[Parser[A]] = { p: Parser[A] =>
    val maybeResults = p(LineStream[Eval](input)).value

    maybeResults match {
      case Right(actual) =>
        val actualList = actual.toList
        val permuted = actualList flatMap { a => results map { b => (a, b) } }

        val compared = permuted filter {
          case (a, b) => a == b
        }

        (results.length == actualList.length && compared.length == results.length,
          s"accepted '$input' with results $actual",
          s"accepted '$input' but produced results $actualList, expected $results")

      case Left(err) =>
        (false, "", s"failed to parse '$input' with error $err")
    }
  }

  final def failToParse(input: String)(errors: ParseError*)(implicit W: Whitespace): Matcher[Parser[_]] = { p: Parser[_] =>
    val maybeResults = p(LineStream[Eval](input)).value

    maybeResults match {
      case Right(results) =>
        (false, "", s"failed to reject '$input' with results $results")

      case Left(actual) =>
        val permuted = actual flatMap { a => errors map { b => (a, b) } }

        val compared = permuted filter {
          case (a, b) => a == b
        }

        (errors.length == actual.length && compared.length == errors.length,
          s"rejected '$input' with errors $actual",
          s"rejected '$input' with errors $actual, expected $errors")
    }
  }
} 
Example 10
Source File: DiffMatcher.scala    From diffx   with Apache License 2.0 5 votes vote down vote up
package com.softwaremill.diffx.specs2

import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent}
import org.specs2.matcher.{Expectable, MatchResult, Matcher}

trait DiffMatcher {
  def matchTo[A: Diff](left: A)(implicit c: ConsoleColorConfig): DiffForMatcher[A] = DiffForMatcher(left)

  case class DiffForMatcher[A: Diff](right: A) extends Matcher[A] {
    override def apply[S <: A](left: Expectable[S]): MatchResult[S] = {
      val diff = Diff[A]
      result(
        test = {
          diff.apply(left.value, right).isIdentical
        },
        okMessage = "",
        koMessage = diff.apply(left.value, right) match {
          case c: DiffResultDifferent =>
            c.show
          case _ =>
            ""
        },
        left
      )
    }
  }
}

object DiffMatcher extends DiffMatcher 
Example 11
Source File: TwoSumTest.scala    From coding-interview-questions-scala   with Apache License 2.0 5 votes vote down vote up
package org.questions.arrays

import org.specs2.matcher.Matcher
import org.specs2.mutable.Specification
import org.specs2.specification.Scope


class TwoSumTest extends Specification {

  class Context extends Scope {
    val arrayUtils = new TwoSum

    def sumTo(sum: Int): Matcher[(Int, Int)] = (_: (Int, Int)) match {
      case (a, b) => a + b must be_===(sum)
    }
  }

  "under 2 elements in seq" should {
    "be none" in new Context {
      arrayUtils.findPairSum(Seq(1), 8) must beNone
    }
  }

  "two elements that sum" should {
    "return these elements" in new Context {
      arrayUtils.findPairSum(Seq(2, 3), 5) must beSome(sumTo(5))
    }
  }

  "two elements that doesn't sum" should {
    "return these none" in new Context {
      arrayUtils.findPairSum(Seq(2, 3), 4) must beNone
    }
  }

  "more elements that sum" should {
    "return the sum" in new Context {
      arrayUtils.findPairSum(Seq(1, 2, 3), 5) must beSome(sumTo(5))
    }
  }
} 
Example 12
Source File: DeserializationRoundtripSpec.scala    From twitter4s   with Apache License 2.0 5 votes vote down vote up
package com.danielasfregola.twitter4s.entities

import com.danielasfregola.twitter4s.helpers.{FixturesSupport, JsonDiffSupport}
import org.json4s.native.Serialization.writePretty
import org.json4s.native.{JsonParser, Serialization}
import org.json4s.{JNothing, JValue}
import org.specs2.matcher.{Expectable, Matcher}
import org.specs2.mutable.Specification
import org.specs2.specification.core.Fragment

import scala.reflect._

class DeserializationRoundtripSpec extends Specification with FixturesSupport with JsonDiffSupport {

  "JSON deserialization" should {

    def roundtripTest[T <: AnyRef: Manifest](jsonFile: String): Fragment = {

      val className = classTag[T].runtimeClass.getSimpleName

      s"round-trip successfully for $className in $jsonFile" in {
        val originalJson = load(jsonFile)

        val deserializedEntity = Serialization.read[T](originalJson)

        val serializedJson = Serialization.writePretty[T](deserializedEntity)

        originalJson must beASubsetOfJson(serializedJson)
      }
    }

    roundtripTest[User]("/twitter/rest/users/user.json")
  }

  def beASubsetOfJson(otherJson: String): Matcher[String] = new Matcher[String] {

    def apply[S <: String](t: Expectable[S]) = {
      val alpha: JValue = JsonParser.parse(t.value)
      val beta: JValue = JsonParser.parse(otherJson)

      jsonDiff(alpha, beta) match {
        case diff @ JsonDiff(JNothing, _, JNothing) =>
          success(s"""${t.value}
               |is a subset of
               |$otherJson
               |${renderDiff(diff)}
             """.stripMargin,
                  t)
        case diff =>
          failure(s"""${t.value}
               |is not a subset of
               |$otherJson
               |${renderDiff(diff)}
             """.stripMargin,
                  t)
      }
    }

    private def renderDiff(diff: JsonDiff) = {
      val changed = diff.changed.toOption.map { c =>
        s"""Changed:
           |${writePretty(c)}
          """.stripMargin
      }
      val deleted = diff.deleted.toOption.map { d =>
        s"""Deleted:
           |${writePretty(d)}
          """.stripMargin
      }
      val added = diff.added.toOption.map { a =>
        s"""Added:
           |${writePretty(a)}
          """.stripMargin
      }

      (changed ++ deleted ++ added).mkString
    }
  }
}