37 ms·
Mocking a JavaScript class with Jest: mocking vs. dependency injection
- bayesian_horse 4y agoI like using redux: no mocking necessary. Everything is just functions, completely trivial to test. If you use redux-saga you can even test async flows without mocking anything like server responses, browser apis and so on.
- throwway232 4y agoPerhaps I'm a masochist but I don't find these types of tests valuable. Fuzzing, e2e, property testing, & strict type system seem more worthwhile in my opinion.
- doorman2 4y agoAgreed. When you mock, you're essentially writing your implementation twice. Once for the real implementation, and again via the mock interactions. Sometimes, this type of test is helpful in the way that you can catch typos by writing the same piece of prose twice. However, these tests quickly become burdensome to maintain, e.g. changing an implementation in a way that produces identical results requires updating tests even though the behavior is unchanged.
- postalrat 4y agoI hate mocks. But all testing is about writing at least your intention twice and hoping you got it right at least once.
- pan69 4y agoI didn't read the full article (yet), I only skimmed it so far. I think the author is trying to demonstrate one or more principles, he/she does that with simple examples so not to overload the reader. So, yes. Glancing over the examples, they might seem futile but I would assume that is a much larger application with a lot more complexity that the principles demonstrated become more useful. Another example of this is explaining Object Oriented Programming, which with simple examples seem bloated and can be achieved with a much simpler approach. However, in the context of a large, decoupled enterprise system its a totally different thing.
- oxff 4y agoMocking almost certainly is always the wrong thing to do. The maintenance burden alone from making sure the mock accuracy wrt. real implementation is high enough to guarantee high enough assurance should disqualify using them. They also make the tests nearly unreadable and very hard to reason about IME.
- lamontcg 4y ago> To test a piece of code swiftly and have consistent reliable results all the other dependencies have to be replaced with mocks controlled by the software engineer writing the tests. For example, if you are testing a function that relies on a file in the file system or makes an HTTP call over the network these external resources must be replaced with mocks for the tests to be a unit test. Relying on any external resource can make the test results flaky and hence unreliable. For both of those examples you can: 1. Create a temp file in a temp dir and inject the tempfile (probably the name of the tempfile) into the object under test. So if your object edits and manipulates /etc/passwd you can create a real file with some passwd contents (or a built-in fixture checked into the tests) and then point the object at that instead of /etc/passwd. Then you don't have to mock the whole POSIX API. Since you create the file you can rely on it existing. 2. Write a minimal API which runs on localhost with minimal or no state, no threading, possibly no auth (although something elsewhere in your tests needs to test auth) or anything else, which your code can setup expectations and responses on then point the object at the "server". Since you create this service in your tests you can rely on it existing and it shouldn't be brittle. For other objects you can expand the System-Under-Test (SUT) so that you construct multiple objects and feed them into the test. This is particularly true when the object you are testing sends messages to other objects and then expects to get changed state back out of them. Often better to just construct a real object rather than a mock. In a perfect world you might refactor everything so perfect unit testing was possible, but in reality you just won't be able to do this. And in general, unit tests are often useless because of the mocking and because if the contract changes on one side and not the other then you wind up shipping broken code. They're fast, which is great, which means you can enumerate all of the edge cases, but some kind of functional/integration testing that uses larger bits of the system together is almost always better in terms of giving confidence that you're shipping code that works. And with an infinitely fast CI system I'd argue that you should never write unit tests and you should be spinning up real APIs in your test harness and hitting them with real client workflows. Instead I write a lot of fast unit tests, although I'm real quick to jettison the idea that a unit test only covers one single object under test, and if the result isn't "really" a unit test, I'll let the philosophers sort it out.
- Gabriel_h 4y agoYou may find this interesting too https://meticulous.ai/blog/let-users-write-tests-for-you/ https://meticulous.ai/blog/let-users-write-tests-for-you/ r.e. e2e testing and automatically generating mocks for UI tests. Maintaining mocks in e2e tests often becomes infeasible with a meaningful amount of e2e tests.
- paulddraper 4y agoAccurate. Unit tests are valuable if you're writing something with a very well defined interface like a base64 encoder. But bugs usually happen in the "glue". Static checks and e2e pound for pound catch the most bugs with the least effort.
- bern4444 4y agoIn all the tests I've written mocking always sucks. It's not hard, but it becomes a rabbit whole time suck. My personal goal is to only mock the server and as low as possible or necessary. I'm fully convinced mocking is a code smell of a bad implementation. It means the abstraction is leaky or too complex and would be better off being broken further down. I also abide by the rule of not exporting or making something public just for the sake of testing it. Property tests are useful but can be tricky to identify, and a good type system that is well used removes the need for the majority of unit tests. No more, validate this function returns a number, or a string. The compiler will test and guarantee that for you. Invoke the function under test and assert on its return value. This works in the majority of cases but when you do need to reach for something stronger, JS can do some fun stuff via reflection. An example. We want to test a function that consumes an object which is used to make an HTTP fetch call, building the `Request` object from the passed object. // Of course we mock the fetch function // or would have a mock server running via something like msw. // Tests should never require an internet connection to run. window.fetch = jest.fn().mockResolveValue(new Response()); const config = { url: 'https://myapi.com' }; // Invoke the function under test await myFunctionThatInvokesFetch(config); // We can access the mocked fetch function and get the argument with which it was called - // the built up Request object from the argument passed to the function under test. const requestObjectUsed = window.fetch.mock.calls[0][0]; const realRequestObject = new Request(requestObjectUsed); // Validate that if no method is specified in config, we default to a GET request. expect(realRequestObject.method).toBe('GET'); Lastly, if a function modifies one of its arguments, the function should include that modified value as part of it's return value. The same could be said for having a function invoke other functions. Rather than assert a function is called, have it return something that indicates it was called. This makes testing a breeze and is also a hint to the invoker that something was done to the thing passed to it.
- jonahx 4y agoMocking a global function in that manner is a poor solution. Two better options: 1. If you are testing that the proper Request is built from your config, and that's hairy logic you want to test, pull that logic out as as a pure function (config => Request) and test that. 2. If you really want to test "through" the current function, explicitly make the fetch function one of its dependencies, passed in as an argument, with a default value of "window.fetch" which is used in production. Now you can keep your current approach but what you are doing is explicit, and requires no global object overrides for your tests.
- rockwotj 4y agoFakes > Mocks! Post: https://tyrrrz.me/blog/fakes-over-mocks https://tyrrrz.me/blog/fakes-over-mocks Previous HN Discussion on the above post: https://news.ycombinator.com/item?id=24770954 https://news.ycombinator.com/item?id=24770954 This does require dependency injection (DI), and my hot take is that manual DI > framework magic. Sure it's a little extra code writing, but folks should optimize for reading code, and some of the frameworks out there have a lot of complex concepts for DI (For example dagger is a popular DI framework for JVM, and the dev-guide has a lot of stuff in it, which feels like a lot to learn for what it's providing: https://dagger.dev/dev-guide/ https://dagger.dev/dev-guide/, no knock against dagger, I've ran into this with other DI frameworks too!)
- sethammons 4y agoGlad to see one more article on fakes > mocks. Here's mine that is not quite as good and focuses on Go from four or so years ago: https://sendgrid.com/blog/when-writing-unit-tests-dont-use-mocks/ https://sendgrid.com/blog/when-writing-unit-tests-dont-use-m...
- williamcotton 4y agoOne approach I’ve found useful is publishing a test suite as a module for use in these kind of “fakes”. For example, a file system blob store that passes the same test suite as an S3 blob store can be used as a reliable fake. https://github.com/maxogden/abstract-blob-store https://github.com/maxogden/abstract-blob-store
- leidenfrost 4y agoInteresting. However, I find mocks useful when I try to replicate error handling in case of unexpected exceptions. How do I replicate that with a Fake? A flag for error triggering?
- sethammons 4y agoIn Go, I create a struct with two properties: err and value. When err is set, it returns that, else returns the value. In the test, I create the fake and set its err to whatever and then verify handling. As needed, you can add properties and add a delay to the response if set.
- davesque 4y agoWhy can't we just "make fun" of JavaScript classes with "jokes?"
- azangru 4y agoHere's what I don't quite understand about mocking in Javascript. When I write a frontend application using components (React, Preact, web components — doesn't really matter), I will often find myself in a situation when I have a component tree like this: A --> B --> C In which I want to test component A; but one of its grand(-grand-etc.-)children down the tree (in this case, component C) is quite complex; and setting up a test for component A in such a way as to accommodate for the dependencies of component C would be cumbersome. Normally, I would just mock component C out entirely, using Jest's module mocking ability. However, this comes at a cost. ES6 import statements are static; and thus, when Jest finally completes its transition to ES6 modules, it won't be able to just mock plain static imports. The likely solution is that the rules for mocking will change; and if one wants to mock modules, he would have to do so via dynamic imports. Which means I'll have to go through all my tests and rewrite them :-( I have not yet seen a good and clean story for mocking Javascript modules. A more standards-compliant project, Web Test Runner, does module mocking via import maps [0]; but this looks both clunky and not quite suited for per-test mocking. Which leaves me confused. If mocks are evil, how do I test my components? And if they are fine, what's the future-proof standards-compliant way of module mocking in Javascript? 0 - https://modern-web.dev/docs/dev-server/plugins/import-maps/ https://modern-web.dev/docs/dev-server/plugins/import-maps/
- MrPatan 4y agoI don't unit-test ui components. Putting complex logic in ui components is a mistake anyway. Do test end to end.
- JyB 4y ago> Do test end to end. What do you use concretely (and how) for this?
- rockwotj 4y agoCheckout https://playwright.dev https://playwright.dev
- sethammons 4y ago
- Aeolun 4y agoI just cannot for the life of me ever understand a test with mocks in. Mocking things at the module level seems incredibly convoluted to me. You constantly go off and look at where the imports are coming from and trying to keep all that in line.