> But if the tests verify that the output is correct, then it implicitly follows that the types are correct, so you get type checking for free.
Your unit test checks the correct behaviour for one out of infinite possible inputs. You don't 'get type checking for free' , what you get is no type checking at all.
If I understand you correctly, you a suggesting unit tests in a dynamic language should test all possible invalid inputs. Since statically typed languages eliminate a subset of invalid inputs (because they won't compile), statically typed languages saves you from writing some of these tests.
My argument is that you wouldn't write such tests in the first place since they provide little to no value.
If function `foo()` calls function `bar()` you want to ensure that foo passes the correct arguments. You don't do that by checking that bar() rejects all possible invalid types. You do it by checking that foo() actually works and provide the correct result. The types involved are an implementation detail.
But you're testing a tiny subset of possible scenarios of behaviour of the function. If you can anticipate all possible input types and value ranges, maybe unit tests are enough, but that's not realistic in a dynamic language/program where complex values are non-deterministically generated at runtime - based on user input, db, file, etc.
If you are talking about untrusted external input then yes, you need to carefully validate and reject invalid input. If you receive JSON or a CSV file or whatever, there are an infinite number of valid inputs and an infinite number of invalid inputs. But this is the same for statically typed languages, and the type-checker will not help you, since a valid JSON string and an invalid JSON string look exactly same to the type checker.
That's not at all the same problem, because here you're intentionally writing code that throws exceptions for some reason? Also, many languages, such as Java will not allow you to throw a checked exception that's not declared on the interface method - such issues will be statically checked and caught - thanks for proving my point ;)
> of valid inputs and an infinite number of invalid inputs. But this is the same for statically typed languages, and the type-checker will not help you, since a valid JSON string and an invalid JSON string look exactly same to the type checker.
Not at all, in a statically typed language the JSON would typically be deserialized into a structured type with all the benefits of type checking, validation and usage of the resulting values. Of course it's possible to just use a JSON string directly, but that's not idiomatic and generally not the way it's done in quality codebases.