[LRUG] Joined-up unit tests

Yevhenii Kurtov yevhenii.kurtov at dyad.net
Fri Jun 7 07:00:16 PDT 2024


Hey Patrick,

The topic that you're raising here deserves reading two books I want to
mention here:

- Growing Object-Oriented Software guided by Tests
https://www.amazon.co.uk/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627
- The Art of Unit Testing
https://www.manning.com/books/the-art-of-unit-testing-third-edition

GOOSE was a mind opening in terms of using mocks. I can't tell I fully
comprehended it. That kind of literature deserves a couple passes.
Right now I'm going through The Art of Unit Testing and it's
absolutely delightful, even though the latest edition is using JS for
illustrating implementation techniques.


Best,
Yevhenii

On Thu, 6 Jun 2024 at 23:13, Kozubink rejestla <rejestlakozubink at gmail.com>
wrote:

> okay
>
> On Thu, Jun 6, 2024 at 3:32 PM Tom Chipchase via Chat <chat at lists.lrug.org>
> wrote:
>
>> First of all, I wouldn't get too hung up on deciding that because you're
>> doing unit testing, you need to mock B. If its not slow, not difficult to
>> set up in a way that it will return valid responses, and not making
>> requests to external services you don't want to (or can't) do during your
>> CI pipeline, you can just call a real B. (not an exhastive list, there are
>> other valid reasons for mocking).
>>
>> Secondly, you probably don't want to do `expect(B).to
>> receive(:call).with(x).and_return(y)`. Its conflating two things. If the
>> point of the test is that A does some stuff, and as a result calls B, and
>> you're test is asserting that B is called with some parameters, it doesn't
>> matter what the return object is. If A calls B to get `y` and then do some
>> other things with it, you don't need to `expect` it to happen; something
>> else in your test should fail if it doesn't, so you just need to `allow`
>> instead of `expect`.
>>
>> Pact has already been mentioned, and that is the path I would go down.
>> I'm not aware of a similar tool that works at the rspec mock level, but you
>> could probably get something close. The key point is that everywhere you
>> have `allow(B).to receive(:call).with(x).and_return(y)`, there should be a
>> corresponding test that asserts that if B is called with x, it does in fact
>> return y. If you combined that with a previous suggestion of defining all
>> your mocks in one place, and using rspec's shared_examples, you could run
>> the same shared example against the real B, and the double of B to ensure
>> they behave the same (or close enough that the tests using your doubles
>> don't care about the difference). The one bit of discipline required would
>> be that if you mock something new, you need to write a spec too.
>>
>> Lastly, this is a lot easier when the code your testing has been written
>> with this approach in mind from the start. A key thing with pact is that
>> the consumer side is written first, and that's how I'd approach this too.
>> If you're just adding mocks to existing specs in order to isolate them,
>> you'll just end up with very brittle specs.
>>
>> On Thu, 6 Jun 2024, at 11:51 AM, Patrick Gleeson wrote:
>>
>> Hi LRUG,
>>
>> I've been bitten by bad unit tests in the past (mostly ones I've written
>> myself), and lean towards integration tests (a la
>> https://x.com/rauchg/status/807626710350839808) when given the choice.
>> But I want to believe good unit tests are achievable. A problem I often
>> encounter is something like this:
>>
>> Service A calls service B. The unit tests for service A mock service B
>> using statements like "expect(B).to receive(:call).with(x).and_return(y)".
>> But in reality (due to refactors over time or misunderstandings between
>> teammates), if you passed x to service B you'd get a return value of z, or
>> it would raise an ArgumentError. So the unit tests for A are  only passing
>> because they're describing an impossible situation.
>>
>> Are there any good patterns for constraining the arguments and return
>> values stated in mocks somehow? I'd love it if something would check that
>> the inputs and outputs I specify when mocking service B have been "proven"
>> to be accurate in my unit tests for B. That way my unit tests could give me
>> some of the benefits of (slower, unwieldier) integration tests "for free".
>> Or am I being hopelessly naive and misguided here?
>>
>> Patrick
>> *Mediocre developer. Failed composer. Fledgeling novelist
>> (https://bedfordsquarepublishers.co.uk/book/hattie-brings-the-house-down/
>> <https://bedfordsquarepublishers.co.uk/book/hattie-brings-the-house-down/>).*
>>
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>>
>>
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>>
> _______________________________________________
> Chat mailing list
> Chat at lists.lrug.org
> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20240607/c7933b0e/attachment.htm>


More information about the Chat mailing list