zio-http icon indicating copy to clipboard operation
zio-http copied to clipboard

`HeaderValues` are not usable in tests

Open nartamonov opened this issue 3 years ago • 6 comments

Since HeaderValues is a public object and contains constants for often-used values, the first intent of a user is to reuse the constants in his tests, something like:

import zhttp.http.*
import zio.test.*
import zio.test.Assertion.*
import zio.*

object TestSpec extends DefaultRunnableSpec {
  val spec = suite("TestSpec")(
    testM("") {
      val sampleApp = Http.collectHttp[Request] {
        case _ => Http.text("OK")
      }

      val r = Request(Method.GET, URL(Path("/a/b/c")))
      assertM(sampleApp(r).map(_.contentType))(isSome(equalTo(HeaderValues.textPlain)))
    }
  )
}

It compiles fine, but unfortunately it doesn't work because HeaderValues are not Strings, but some optimized objects (io.netty.util.AsciiString). Both Strings and AsciiStrings implement CharSequence, but they are not comparable. So we get unexpected and non-obvious (especially for novices) failure:

[info] - TestSpec
[info]   -
[info]     text/plain did not satisfy equalTo(text/plain)
[info]     Some(text/plain) did not satisfy isSome(equalTo(text/plain))

Possible (and ugly) workaround is to call .toString on HeaderValues.textPlain to convert it to plain string:

//...
      assertM(sampleApp(r).map(_.contentType))(isSome(equalTo(HeaderValues.textPlain.toString)))
//...

I think that issue should be somehow mitigated, some possible ways:

  • We could explicitly document that HeaderValues shouldn't be used in tests when you want to check some internal part of Response.
  • We could refactor so that such attempts to compare internal parts of Response (headers or something else) with HeaderValues is catched by the compiler during type checking.
  • We could sacrifice these optimizations in order to gain ergonomic improvements for end users. For example, Akka HTTP doesn't bother with optimizing those constants, as far as i know.

In my oppinion 3rd option is the best, since I don't believe zio-http gains much performance improvements by those optimizations. But I could be biased here :-)

nartamonov avatar Feb 10 '22 15:02 nartamonov

Which version of ZIO Http are you using. I think contentType and HeaderValues.textPlain both return CharSequence

tusharmath avatar Feb 11 '22 12:02 tusharmath

I use 1.0.0.0-RC24. Yes, contentType and HeaderValues.textPlain both return CharSequence, but they are not comparable however:

import zhttp.http._

val rs = Response()
  .updateHeaders(_.addHeader(HeaderNames.contentType, HeaderValues.textPlain))

rs.contentType == Some(HeaderValues.textPlain) // evals to false

I think it's because headerValue converts actual header value to plain String:

  final def headerValue(headerName: CharSequence): Option[String] =
    header(headerName).map(_._2.toString)

Of course, it could instead return header value "as is", but then it will not be comparable to plain strings:

import zhttp.http._

val rs = Response()
  .updateHeaders(_.addHeader(HeaderNames.contentType, HeaderValues.textPlain))

rs.contentType == Some("text/plain") // currently it evals to true, so it would be good if it remains so after the fix.

nartamonov avatar Feb 11 '22 13:02 nartamonov

Hmm, that makes sense. What if we can create a specialized assertCharSequence and assertCharSequenceM operator that works on CharSequence?

tusharmath avatar Feb 20 '22 13:02 tusharmath

Or even better, what if we will specialize existing equalTo combinator to properly handle CharSequences? Something like:

  final def equalTo[A, B](expected: A)(implicit eql: Eql[A, B]): Assertion[B] =
    Assertion.assertion("equalTo")(param(expected)) { actual =>
      (actual, expected) match {
        case (left: Array[_], right: Array[_]) => left.sameElements[Any](right)
        case (left: CharSequence, right: CharSequence) => left.toString == right.toString
        case (left, right)                     => left == right
      }
    }

I can try to propose a PR to upstream zio-test library.

nartamonov avatar Mar 10 '22 13:03 nartamonov

That would be the best!

tusharmath avatar Mar 13 '22 06:03 tusharmath

Created https://github.com/zio/zio/issues/7103

gciuloaica avatar Jul 20 '22 07:07 gciuloaica

Is there anything left to do for this ticket since https://github.com/zio/zio/issues/7103 was merged?

swoogles avatar Sep 13 '22 18:09 swoogles

@swoogles the change it is included as we are on zio 2.0.2, so nothing left to be done.

gciuloaica avatar Sep 14 '22 05:09 gciuloaica