5 ms·
Let’s say I have a newsletter generation service for a website. Users can opt of emails. I use AWE SES for sending emails. I would like to test that emails are
by mistahenry 7y ago
Let’s say I have a newsletter generation service for a website. Users can opt of emails. I use AWE SES for sending emails. I would like to test that emails are sent unless you’ve opted out.
First, I don’t know how to accomplish this without mocking / replacing the email dependency before testing, even in an integration style test. Emails cost and how would I even verify if I did send the email. Second, I really am failing to see how this is poor design and am genuinely curious.
I can think of so many scenarios where some “orchestrating” service uses multiple single unit services to pull off some task. To me, mocking is incredibly useful for ensuring that you test a lot of failure cases in these scenarios by mocking to force an exception.
A lot can be accomplished via integration tests rather than mocking (actually using a database for example). But to somehow suggest mocking is across the board bad seems like an illogical position
- 0x445442 7y agoInstead of relying on a mocking framework to mock the injected service, can you inject a service which implements an interface and then supply whatever type of implementation you like depending on the context you'd like to test? This is one of the neat things about Java 8 Functions. I'm not sure about what their design decisions were but effectively they backed into coding to interface vs. implementation, which used to be a solid design principle.
- LgWoodenBadger 7y agoYou could, but it’s tedious to write a new class for every scenario you want to test. Or to program for every scenario within that test implementation. And now you kind of should test your test implementation, and who wants to do that? Mocking makes it quick, easy, straightforward, and cuts out a ton of code noise from the test.
- sasmithjr 7y agoThe email service I use has a library that allows me to set the Host as a constructor parameter, so I have an EmailHost environment configuration value that I change. It's localhost for dev/testing and set to the correct host for production. My test code then listens for the API calls from that library, uses a different library to parse the email, and then I can verify the content. No code changes between dev, test, and production, but I added an environment variable to represent the e-mail host so that I can change where e-mails are sent as needed. I dislike mocking, so I took the time to figure that out and set it up. If mocking works for you, I likely wouldn't go out of my way to change things to get rid of it.
- commandlinefan 7y agoWait a minute, your test code spins up a SERVER? Dear God, that’s just horrible… Sorry, man, but that’s just… horrible.
- kixiQu 7y agoThat's actually really common for distributed systems stuff as a way to run something in between full integration and unit tests. I think there's some official support for, e.g. local file stuff mimicking the interface of DynamoDB.
- hderms 7y agoI think separating code into 'algorithms' that make a decision based on pure data, returning the decision in the form of pure data, 'side effects' which represent actions which can occur (and are also very mockable), and then 'coordinators' which rig these things together. In a dynamically typed language this pattern can be more challenging because there is a lot more burden to prove that the integration of various components is rigged up properly, but in a language like Scala this can work incredibly well. The bulk of the testing in this design would be easy to write unit tests around the 'algorithm' functions. The coordinators have almost zero logic in them and so are mostly just there to coordinate components together and generally demand less unit testing. Since the coordinators coordinate actions and algorithms then they'd likely have dependencies (the actions in this case, generally) passed in using constructor injection or parameters to a function.