Dependency Injection
An OrderConfirmation class builds its own new SmtpMailer() in the constructor, so every test needs a live mail server and switching providers means editing the class itself. The trap is building what you could be handed.
- ▸A class that builds
new SmtpMailer()inside itself can't be tested without a real mail server. - ▸Moving one
newfrom a field to a constructor parameter is the move; testing and swapping fall out of it. - ▸A so-called "DI framework" only automates wiring you could type by hand in
main(); the parameter is the real technique.
Here is an OrderConfirmation class with one line in it: private readonly SmtpMailer mailer = new SmtpMailer();. It reads as harmless. Then you try to write a test. The test calls confirm(order), which calls mailer.send(...), which opens a socket to a real mail server. There is no mail server in your test run, so the test either hangs or fails, and even if it passed it would be firing real email at every run.
Look at why you are stuck. The SmtpMailer is built inside the field initializer, so nothing outside the class can reach it. The constructor signature is empty: OrderConfirmation() takes no arguments, so a caller reading the signature has no idea this class talks to a mail server at all, and no place to hand it a different one. The concrete SmtpMailer is welded into the class definition. To use anything else, a fake for the test, a different provider in production, you have to open OrderConfirmation and edit the line. Every class that builds its own collaborators this way carries the same weld.