etorreborre/specs2

Make `FutureMatcher` more first-class?

Closed this issue · 2 comments

I've been tripped up by this a couple times: to be specific, that FutureMatcher is a significantly underpowered version of Matcher e.g. in etorreborre/specs2-cats#18 (comment), where creating a FutureMatcher doesn't use AsResult, in another case I tried using returns, beLike, etc. but that didn't seem to work, etc.

I know these issues can be patched as they come up, but I wonder if this is a more fundamental issue that Matcher and FutureMatcher form two unrelated hiearchies AFAICT making it difficult to enforce consistent behaviour between them.

Maybe it's too crazy, but if specs2 takes an async-first approach, maybe FutureMatcher should be a top the top of the hiearchy, and everything else should be implemented in terms of that? E.g. Matcher is just a FutureMatcher where all the Future's are already completed via Future.successful (or Future.failed maybe).

Thanks for the suggestion, I need to think about this. I'm kind of reluctant because that might have some large impacts on the current situation and maybe the biggest one would be that an execution context might be necessary everywhere. And for the specifications that are just testing pure, synchronous code that would be superfluous.
I am also not sure how that would play with mutable specifications where matchers can throw exceptions.

So for now I'd rather leave things as they are where FutureMatchers is a shortcut for Future-like data type (they're aren't so many of them), to avoid having to write:

IO(1).map(v => v must beEqualTo(1)).unsafeToFuture()

Thanks for considering this! Just a note:

maybe the biggest one would be that an execution context might be necessary everywhere. And for the specifications that are just testing pure, synchronous code that would be superfluous.

I agree it would be superfluous, but I don't think it is necessary actually. For pure/synchronous operations on Matcher, you can delegate to the FutureMatcher implementation by providing the parisitic execution context which runs it on the current thread.

https://www.scala-lang.org/api/3.1.0/scala/concurrent/ExecutionContext$$parasitic$.html.

So for now I'd rather leave things as they are

👍