How to mock, or sneak out of it

I gather that mocking in tests is a bit frowned upon around here, and I do get the arguments, but I’m a bit stuck in the practicalities of it.

So if more experienced minds would talk me through this example, it would be much appreciated.

I have a small Kemal project, using multi_auth for authentication, and I’d like to test it a bit with spec-kemal.

I’d like to test that my Kemal::Handler redirects un-authenticated users to the login page, and that authenticated users gets the right data.

The first is very easy, but obviously the authenticated user case… Presents some problems.

So at first glance, it looks like I either have to a) mock MultiAuth, or b) fiddle with Kemals session to convince it I’m logged in. Option b) sounds even more dirty than using a mock, so I’m leaning towards a), especially as multi_auth presents a very simple interface.

Or am I barking up the wrong tree?

Where I come from, the answer to this challenge would be dependency injection. But in that world, I would most likely have an App class that gets passed what it needs when instantiated, which could include the “authenticator”. But I don’t have such a class in my Kemal app, so what’s the most Crystal way?

In a perfect world I think mutli_auth would allow you to supply a custom HTTP::Client instance that would be set on the underlying OAuth2::Client instance. This would allow you to use a mock testing implementation client that keeps tracks of the incoming requests that let you later run assertions on them, and returns pre-configured responses, e.g. a response that returns a fake token.

However, mutli_auth does not currently support that out of the box, nor is there a built-in testing HTTP client. The closest you can get is probably GitHub - manastech/webmock.cr: Mock HTTP::Client, which should allow you to mock the request/response w/o touching any internals (as it does that for you).

Related: Redesign HTTP::Client to implement missing features · Issue #6011 · crystal-lang/crystal · GitHub

But yes, DI usually the answer when it comes to this kind of stuff, but it’s not all that common of a pattern in Ruby/Crystal it seems :confused:.

Related: [RFC] Standardized Abstractions & shameless plug.

Well, that’s what I’m used to (DI’ing most things, not a perfect world). The thing is, it’s enforced DI, so you end up passing bloody everything to everybody, and end up with DI containers to keep track of everything.

In PHP there’s no built in HTTP client, so there’s muliple packages (Guzzle, Symfony HTTPClient, HTTPlug to name a few). Then they did a PSR standard defining a common interface, so now there’s a package that’ll autodetect which clients have been installed so a library can work without requiring a HTTP client being injected by the user. Which is handy if you just want a Github API client without messing with the details, but all kinds of hell when you try to figure out why it picked that one.

But I digress. Yeah, mocking HTTP would be one way, but seems to be a bit of a big hammer. I’ve done integration testing using HTTP client mocks before and still wake up screaming from time to time.

I’m not particularly interested in testing multi_auth and the OAuth handling, so I guess I could just redefine MultiAuth.make. Or (by clever placement of requirements) replace it with a mock implementation.

If you plan on mocking, have you checked out Spectator?

2 Likes

@mdwagner No, wasn’t aware of it.

The mocking looks nice, but I’m a bit averse to switching test framework just for mocking.

I managed to get some working tests by overwriting MultiAuth in my test, and I don’t think it looks too bad. I’ll see if I can avoid mocking in general.