The most complete nomenclature I have seen is that used by Marin Fowler (which he attributes to Gerard Meszaros). I like it because it gives a fairly decent taxonomy of test doubles, and thus makes it possible to easily describe some of the common types, and for example, suggest that some other type might be better suited in some situation.

I summarize it in my own words below:

Dummy: Trivial implementation that might do nothing, return some default value, or throw exception if called. These are intended to be used only when an object gets passed but never used.

Fake: Have a working implementation, but takes some shortcut that makes them unsuitable for use in production. These shortcuts can potentially be pretty extreme, like a fake sales tax service that just uses a flat percentage for everything, an image watermarker that just returns the original image, etc. Basically if one could substitute the original implementation for this, still be spin up and use the whole app, without worrying about it crashing/misbehaving (such as hitting some unimplemented case or fixed return data causing map collisions etc), then it is a fake.

Stub: Harded implementation. Is not fully functional, and designed to have correct or usuable responses only for scenarios that the test will care about. (Sometimes designed to take into account a small group of tests).

Spy: A spy is an implementation that records some information about how it is used. It could be a stub or a fake. IT could also be a decorator patten wrapper around some other implementation. (Fowler incorrectly oversimplifies this by defining it to be a stub implementation). The recorded information can later be accessed to verify say the number of emails the code being tested tried to send.

Mock: An object that gets preprogrammed with expectations about how it will be called (and what it should return). Then after running the test, it is asked to confirm if the calls made upon it match the pre-programmed ones. One can pretty clearly see that this is a bit different than the others, in that it contains special verification logic.

-----

Either a spy or a mock is needed when the behavior being tested is not visible as either part of the return value, or as part of the state of some object after the test is completed. Those cases require "behavior verification", as opposed to "state verification". State verification might check some object's propeties, or query the fake database to see if the resulting state is as expected, while behavior verification.

The need for behavior verification can be common in certain "command" style apis. For example, if a dependency's purpose is to send emails, then it is quite possible that there is no state that remains (within the program) to verify if it got called. So the best that is possible is to verify that the dependency got called (or did not get called) as expected.

(Don't take the state vs behavior distinction too literally. Obviously both Mocks and Spies store recoded information as state. The important concept is that the normal implementations would not include this state.)

---

The article in question on which the above is all loosely based is: https://martinfowler.com/articles/mocksArentStubs.html