Most programs have dependencies that many functions share: a database, a mail server, a payment provider, a cache. Refingere's context handles them without passing them through every function, and without globals or a container library. This page shows the pattern end to end: an interface, a default, the code that uses it, and tests that swap it out.
Passed as parameters, a dependency has to go through every function between where it's made and where it's used, even the ones that never touch it. Held in a global, it can't be changed for one test or one request. A context value avoids both.
Start with an interface for what the code needs from the dependency, and a type implementing it for real. The code that uses it will only ever see the interface, so any other implementation can take its place.
A const marked @injectLocally is provided to every call in its folder. Use @inject for a single file and @injectGlobally for the whole project. This is the one place the real dependency is chosen. See Files and Folders.
&mailer reads the value from wherever it was provided. @require documents that the function uses it, with its type, and can give a default for when nothing provided one - useful when the function can be called from a folder without the injected const. Leave the default out and a caller has to provide it. signUp calls sendWelcome with no mention of mail at all. See @require.
A test injects a fake that implements the same interface. inject is nearer than the folder's const, so it wins for everything the test calls, and ends with the test. The fake records what it was asked to do, and the test checks that. The code under test is the real code, unchanged.
Because an injection lasts until the end of its block, it can scope a dependency to one unit of work: this request's database, a logger for one job, a transaction around one batch. Everything called from the block sees it, and nothing else does. A value injected in one request can't leak into another.
&mailer reads the value from further up, so a function can wrap it - to count, log, retry or cache - and inject the wrapper for the code it calls. The code below sees an ordinary Mailer.
A program's settings usually come from environment variables - the port, a database's address, a timeout. &env.settings(defaults, prefix) fills a struct from them. Each field reads the variable named after it (slowQuery from SLOW_QUERY, after the prefix), parsed as the type of its default value. A field with no variable keeps its default, and a value that doesn't parse is a ParseError naming the variable.
Because it reads &env, a test injects a MemoryEnvironment rather than setting real variables, and the program's whole configuration is checked in one place, at startup.
Use a parameter for the data a function works on, and the context for the services it works with. A good test: if a caller would pass the same value to almost every call, and would only change it for a test or a whole unit of work, it belongs in the context. If different calls get different values, it's a parameter.