How you define "external stuff" matters though. As soon as your function is calling another function, your test can be argued to be an "integration test", as you're now implicitly also testing the logic of the other function.
Alternatively you mock _everything_ and then your "unit test" ends up just being a tautological test asserting that "the code I wrote executes in the way I wrote it". (Not to mention that every time you mock something you are also implicitly asserting what the expected behavior of that thing is).
The only truly reliable tests are E2E tests, but they are too expensive to cover all possible permutations as there are just too many.
This is the catch 22 with testing, and we're always forced to make pragmatic choices about where to draw our boundaries to maximize the value (i.e. actual bugs caught) of them.
Functions, in the sense that there are global variables, is strongly discouraged by the language. It's usually much easier to define a type so there is a clear delineation.
That is, from Mongo, you use Serde and wind up with only valid records operated upon, of a table of such values.
Alternatively you mock _everything_ and then your "unit test" ends up just being a tautological test asserting that "the code I wrote executes in the way I wrote it". (Not to mention that every time you mock something you are also implicitly asserting what the expected behavior of that thing is).
The only truly reliable tests are E2E tests, but they are too expensive to cover all possible permutations as there are just too many.
This is the catch 22 with testing, and we're always forced to make pragmatic choices about where to draw our boundaries to maximize the value (i.e. actual bugs caught) of them.