zio-http
zio-http copied to clipboard
`HeaderValues` are not usable in tests
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 String
s, but some optimized objects (io.netty.util.AsciiString
). Both String
s and AsciiString
s 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 ofResponse
. - We could refactor so that such attempts to compare internal parts of
Response
(headers or something else) withHeaderValues
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 :-)
Which version of ZIO Http are you using. I think contentType
and HeaderValues.textPlain
both return CharSequence
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.
Hmm, that makes sense. What if we can create a specialized assertCharSequence
and assertCharSequenceM
operator that works on CharSequence
?
Or even better, what if we will specialize existing equalTo
combinator to properly handle CharSequence
s? 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.
That would be the best!
Created https://github.com/zio/zio/issues/7103
Is there anything left to do for this ticket since https://github.com/zio/zio/issues/7103 was merged?
@swoogles the change it is included as we are on zio 2.0.2, so nothing left to be done.