Question Details

No question body available.

Tags

architecture packages

Answers (5)

April 5, 2025 Score: 6 Rep: 47,288 Quality: High Completeness: 50%

As long as your mocks only rely on abstractions (e.g. interfaces) for all the bits that interact with logging and the file system, it should be fine to include it in your mail package. In fact, .NET calls this the specified pickup location for it's mail client, so saving emails to file isn't that unheard of.

On the other hand, you could justify the mock belonging to the unit test package if you follow the convention of separating unit tests from the application.

I prefer mocks to live with the test code that needs them. A mock, fake, or stub is a testing concern and therefore should live with the tests. This allows you to add functionality to your mock that makes it easier to write tests, which could include additional dependencies you don't want in the main application. Consider, for a moment, whether writing tests that open and parse email files wouldn't be better off written to iterate through an in-memory collection of email objects, but this implies your unit tests are not actually integration tests in disguise. If your system under test runs in a different process or server, then a) this isn't a unit test, and b) saving emails to a file is probably the preferred approach to testing.

Testing strategy aside, your "mock email client" is actually generating emails with the only difference being it saves the emails to a file. That doesn't sound like a mock to me. It sounds like a potentially useful real implementation of a mail client. Saving emails to a file allows a background process to send them asynchronously, so problems sending emails don't hang up the main application. Whether this extra complexity and effort is worth it for your application is another design matter entirely (see YAGNI). It's just conceivable that saving emails to a file might have uses beyond testing.

In your particular case, you could argue either embedding your "mock" in the mail package or include it with your test code. Pulling it out into a "mocks package" would honestly just irritate me; that seems like separation for the sake of separation because someone said "best practice" without thinking about why that would be better than the alternatives.

April 5, 2025 Score: 3 Rep: 59,765 Quality: Medium Completeness: 30%

Unless you find a justification to deviate from the baseline, in my opinion a mock is owned by the test(s) that use(s) it. Its purpose is defined solely as a tool to help the test execute correctly.

This means your original assertion that the mock belongs to the package is incorrect.

This also resolves the logging dependency. Semantics on what kind of testing you're doing aside, if the reason you're logging things is related to test verification, not application behavior, then the test suite should be the one with the logging dependency.
If the mock is owned by the test suite, that means that the test suite also owns the logging dependency, so that solves that problem.


In extreme cases where you foresee that you will manage many different consumers of your nuget package, all of which will have tests that will want to have a mocked version; there is nothing wrong with extracting that mock into a reusable library or package for all those different test suites to rely on.

But (a) that library/package with the mock does not belong to the original package and (b) spiritually this second package is not an extension of the original package, it's an extension of the test suite (which also happens to need a reference to the original package for the purposes of using its interface type(s)).


Thirdly, don't ask ChatGPT to reason about things. That's not what it is built to do.

April 5, 2025 Score: 1 Rep: 140,596 Quality: Medium Completeness: 60%

For unit testing purposes, I create a mock of the email service from that package which logs raw mail content to a file instead of sending it over SMTP.

There is a misconception here. You don't save files to disk in unit tests.

Let's imagine a practical example. You have a class that deals with emails:

class EmailSender:
    def send(to, body):
        ...

In a different package, you have a class which uses the email sender:

class ReportGenerator:
    def generate(self):
        ...
        self.emailSender.send(user.email, reportBody)

If you are unit-testing ReportGenerator, you create a mock of EmailSender that you inject into ReportGenerator. This mock (which in most languages would be declared in a matter or two lines) would simply check that send method was called with the correct arguments when generate is called. And that's all. Because at the level of ReportGenerator, you simply don't care about the internals of EmailSender. You only need to ensure that generate actually called a correct method, with correct arguments; nothing else.

What EmailSender actually does with those arguments is the responsibility of EmailSender—and it's up to the tests of send method to ensure that it actually does its job correctly. How exactly would you do it depends on a lot of things—the exact implementation of send, the libraries being used, and even the infrastructure.

Emails being a very unreliable communication protocol, chances are, most tests would be both extremely complex (and those would be system tests, and not unit tests) and at the same time irrelevant. I had witnessed a company doing system testing where they actually send an email and check that it's correctly received, just to find out that while it is received when sent to an internal email address, all emails sent to addresses outside the company were silently discarded at infrastructure level.

In essence, you're better mitigating this complexity by calling an API such as Amazon SNS, and only testing that you call those APIs correctly.

April 6, 2025 Score: -1 Rep: 129 Quality: Low Completeness: 30%

The answer to this depends on your programming language (and dependency management tool chain).

Ideally, a package and it's tests (and the mocks needed for those tests) should not be separated (too far), otherwise you have twice the number of packages around, you don't have the context of the package under test when looking at the test code and it may be hard to automatically run the test code when making changes to the package, locally (adding extra hurdles to something like test driven development) and in your CI/CD pipeline.

Some languages (and/or dependency management tool chains) allow you to specify "development dependencies" in one form or another. These are dependencies that need to be installed if you want to develop the package. Usually this includes things like linters, formatters, build tools, tools for building the documentation and, important here, test tools and dependencies needed to run the tests. When deploying the package to production (whether by building a binary, creating a docker container, copying a zip file somewhere, ...) these should not be included.

Python with poetry or UV as dependency manager (and potentially some other ways to manage your dependencies) do allow for this split (actually they allow arbitrary dependency groups and defining which should be installed when, the dev group is just handled correctly by default).

Of course, if your language or dependency management tool chain does not support this, splitting your mocks into a separate package may be the only way to achieve the goal that these dependencies should not be included in your production artifacts. Some additional tooling may then be required to ensure tests are easy to run locally and in your CI/CD pipeline when you make changes to the package.


A different scenario would be if you need the same mocks for the tests of multiple packages. Those should definitely live in some separate testing package.

April 8, 2025 Score: -1 Rep: 11 Quality: Low Completeness: 10%

Using separate mock packages improves test clarity and avoids cyclic dependencies. It encourages a clean architecture by isolating test-only code from production code. It is a good practice in larger projects where modularity, readability, and maintenance of mocks benefit from clear separation.