[Haskell-cafe] Mocking out external services

Matt parsonsmatt at gmail.com
Sun May 19 15:17:50 UTC 2019


Hi Francesco,

You won't have a concrete type for every combination of constraints. For
the most part, your programs will all end up in IO (or some transformer
over IO) anyway. I usually have a "test" transformer and a "production"
transformer (both of which are usually ReaderT Env IO), and instances
defined for each.

It's OK to pass an overly large record to a function. A common style is to
write a huge `data Env = Env { ... }` with dozens of fields, and use
classes to only access the things you need. This allows you to pass smaller
Envs in, but it doesn't require that you write dozens of types preemptively.

I would suggest that `MonadTime` and `MonadKeys` are not great choice for a
Layer 2 - it would be really difficult to write a mock for MonadKeys that
was in any way meaningful. I'd suggest restricting these classes to be
tightly coupled to your domain problem. Instead of getting random keys, try
a sum-type with all the possible things you'd prompt the user for, and a
type for possible user responses. This is much easier to mock meaningfully.

Matt Parsons


On Sun, May 19, 2019 at 4:25 AM Francesco Ariis <fa-ml at ariis.it> wrote:

> (literate Haskell follows)
>
> Hello -cafe,
>     I recently read "Three Layer Haskell Cake" [1] and decided to give
> it a go. I am stuck on a simple problem and feel like I am missing
> something easy.
> One of the first examples is a mocking of an `IO UTCTime` function.
>
> >
> > {-# Language FlexibleInstances #-}
> >
> > data UTCTime = UTCTime Int
> >
> > class MonadTime m where
> >    getCurrentTime :: m UTCTime
> >
> > instance MonadTime ((->) UTCTime) where
> >     getCurrentTime = id
> >
> > -- instance IO omitted
> >
>
> Everything is clear. I decided to write another function, mocking
> a stream-of-Char input.
>
> >
> > class MonadKeys m where
> >     getKeys :: m [Char]
> >
> > instance MonadKeys ((->) [Char]) where
> >     getKeys = id
> >
> > -- instance IO omitted
> >
>
> Again, fine. But now, if I want to write a function which combines the
> two external services, like:
>
> >
> > someFun :: (MonadTime m, MonadKeys m) => m ()
> > someFun = undefined
> >
>
> there is no non-IO instance satisfying the two constraints. I could of
> course write a type
>
> >
> > data Env = Env UTCTime [Char]
> >
>
> and make it instance of MonadTime and MonadKeys, but then I am passing
> non-relevant arguments to a mocked function like getCurrentTime, which
> would only need UTCTime.
>
> What am I doing incorrectly?
> -F
>
>
> [1] https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20190519/e7967e6b/attachment.html>


More information about the Haskell-Cafe mailing list