3

I'm asking myself how verbose I should be when unit testing projects written in untyped languages like Javascript.

Let's take an example for a strongly typed language (C#)

public bool Foo(MyClass myClass) { /* ... */ }

As C# is strongly typed I can be sure that myClass is either null or an actual instance of MyClass (or any instance of a subclass if possible) so my testable values are just these.

Now let's port this method to Javascript:

function foo(data) { /* ... */ }

Now I can't be sure what type data is of as it could be anything.
Inside foo I can check which type data has but to ensure that the function works even with invalid types I would have to test all possible types in my unit tests which produces a lot of extra code.

This topic really bothers me especially in untyped languages where I cannot determine the actual type just by looking at the code. On the one hand I don't want to write a whole lot of code to test every possible type. On the other hand I feel like I'm missing something if I test just the types I'm expecting.

Are there any general recommendations or best practices for this problem?

22
  • 3
    "ensure that the function works even with invalid types" you mean you would write tests that check if it properly rejects invalid types?
    – null
    Feb 19, 2016 at 1:21
  • 3
    @RobertHarvey That blog post is interesting, but I don't agree 100% with. Types definitely substitute for some tests (of course, not all tests).
    – Andres F.
    Feb 19, 2016 at 1:55
  • 2
    @RobertHarvey An advanced type system that allows you to specify non-empy lists would get rid of the test scenario "what happens to this function when it receives an empty list". A type system that allows you to specify union types A | B | C means you don't have to consider any other case in your tests. Of course, like the author of the blog you linked to says, maybe when you're using untyped languages you simply give those guarantees up and don't even want to test for them. But in my experience, that's unwise :)
    – Andres F.
    Feb 19, 2016 at 2:00
  • 3
  • 2
    @WinstonEwert Mainstream languages? I don't know. But I'm pretty sure non-empty lists can be codified using dependent types.
    – Andres F.
    Feb 19, 2016 at 13:35

2 Answers 2

4

Some languages allow you to ensure more conditions at compile time than others. For those condition expressable (and thus ensurable) at compile time, you don't have to test for.

Those conditions that cannot be expressed by the language, or somehow cannot be expressed at compile time, ought to be tested. You will need to test both on the callers' side and callees' side -- that the callers provide valid and don't provide invalid input (to the callees), and that the callees appropriately reject invalid input while accepting valid input (and vise versa for return values).

There is variation among languages, for example, using nullable (or sometimes optional), some languages can move null checking into the compile time ensured set.

It is a matter of degree where and how much type systems eliminate checking. Many conditions cannot be expressed at compile time in most of today's languages (e.g. a list is not empty).

Further, some strongly typed languages make certain weakenings, such as a typedef or type alias that considers two types as equivalent when perhaps they ought to be distinct.

Many popular statically typed languages allow casting to Object and then down casting later. Such downcasting operations also forgo compile time checking, and thus are subject to increase testing requirements.

If you are working in a language with a weak type system, you certainly should look into a general purpose way to test for the presence of all required qualities (of parameters, return values, etc...) and rejection any disallowed qualities.

Type systems are good for what they do, but that being said, a type system will never be able to ensure all possible necessary conditions, and, that also varies by language (even among the strongly typed), and even among those, by the chosen programming style.

3

On the one hand I don't want to write a whole lot of code to test every possible type. On the other hand I feel like I'm missing something if I test just the types I'm expecting.

Honestly, you just need to get over it.

Coders working in dynamically typed languages don't generally worry about what happens when the incorrect types get passed into their function. They don't write extra code to check the types, and they don't write tests to verify what functions do when passed the wrong types. They write pretty much the same tests they would have written had they been working in a statically typed language.

At some level, what happens in a function passed the wrong argument types does not matter, as you won't pass those argument types in a working program. But, obviously, you will accidentally pass them in some cases. Here is the loss you take when you adopt dynamic typing, as it will take longer to identify the problem.

Whether or not this loss is acceptable is a hotly contested question. Advocates of dynamic typing argue that in practice, this kind of error is not overly common and easily detected and resolved. Thus it is an acceptable loss. Advocates of static typing argue that these kind of error is very common, often not easily detectable, and not always trivial to resolve. Thus is it not an acceptable loss.

However, the one thing you shouldn't do is try to program in a dynamically typed language as if it were a statically typed one. Don't try to get the same guarantees you had in a statically typed language. That will only lead to madness. You'll only succeed in making a crappy statically typed language out of a dynamically typed one.

3
  • I guess that is exactly my problem. I'm used to program in statically typed languages for years and I only switch to dynamically typed on occasion for specific tasks or to try something out.
    – TorbenJ
    Feb 19, 2016 at 8:40
  • 1
    @Torben you lose other things, too. Some JS libraries lead to code that's rather cryptic, wrapping every other line of code in yet another closure sometimes borders on obfuscation. IDEs generally don't keep up with the ever evolving libraries and often cannot provide much help in terms of code completion.
    – null
    Feb 19, 2016 at 9:04
  • 1
    @TorbenJonas, I had the same reaction when I started working with dynamically typed languages. What I can say is that when you work with them extensively enough, you can begin to appreciate the flexibility of those languages. I'll also note that javascript is not a great example, as its got a lot of warts which have nothing to do with its dynamic typing. Feb 20, 2016 at 15:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.