In my TDD trainings, I sometimes say that I do not test code that is “too simple to break”; Code where:

  1. The chance of the code breaking is very low
  2. If the code breaks, we would see immediately

What do I mean by that? In this blog post, I’m going to explain…

I wrote the following Typescript code running in #electronjs:

const { dialog, } = remote
const filePath = dialog.showSaveDialogSync({...})
if(filePath) {
    fs.writeFileSync(...)
}

The code is very hard to test, but I’m OK with that, because it fulfills the above criteria.

Hard to Test

Before I go into why the code is “too simple to break”… Why is it hard to test?

Well, “dialog” (and even “remote”) is not available during unit testing. And even if it was, it would open a dialog that I’d have to remote-control. Not cool in a test.

And “fs.writeFileSync” has the side effect of writing the file. To my file system.

If I splitted the original function, I could at least write an integration test for writing the file (call the new function, check if the file exists and has the correct content, delete the file). But would that be useful?

My integration test would only test whether my wrapper function, that only calls writeFileSync, writes a file. So, it would indirectly only test writeFileSync.

Because I don’t want to test framework and library code, this test has no value. What if the test passes? I would know that Node.js does not have a defect in this paritcular function. What if it fails? Could I do anyhting about it, except from filing a bug report?

And even if this integration test had value, what about “dialog”?

“dialog” & “remote” are my bigger problems, but can’t i just mock / stub / fake them? I don’t want to do that because I usually only want to mock my own code. Mocking or stubbing code requires some deep knowledge about the mocked code - I only know my own code well enough.

If I wanted to test the “dialog”, it would have to be an integration test: A test that checks whether one small piece of my code integrates correctly with a framework, lib or 3rd-party system.

Is it Too Simple To Break?

That’s not easily doable here too. I would have to automate this test through the UI. But the code is too simple to break anyway. Why?

The chance of the code breaking is very low

The code does only two things: Open a save dialog, write the file. Everything else - preparing the data, handling the result and edge cases, … must be done by the caller.

I’m hoping that this is write-only code: Code that I write once and never touch again. And I can test all the interesting stuff by writing tests for the caller of this function (which I, of course, did before writing the code above).

And the second criteria:

If the code breaks, we would see immediately

Loading and saving files is some central part of the application. It’s not some obscure feature that people will only use once a year or that only a very small percentage of our users will ever use.

And that’s why not testing the code in a unit test or integration test is OK for me, for now.

If I can separate the hard-to-test parts of my application in small functions that are too simple to break (or almost too simple to break), then I might not need to test them in a unit test or integration test at all. I have dodget the bullet of “hard/impossible to test” code.

What About Easy-To-Test Code?

Here’s a different take in “too simple to fail” in TDD. What about code that is easy to test? Do I apply the too-simple-to-break heuristic here too? Consider a React component that returns:

const tabs= items.map(i => <Tab><Button onClick={i.click}>i.text</Tab></Button>)
return <Tabs>{tabs}</Tabs>

What does it do and what exactly would I test here?

Let’s assume further that the other components, “Tabs”, “Tab” and “Button” are Styled Components that “only” add some CSS but have no functionality.

I could easily test that they are there and structured correctly:

const tabbedPane = mount(<TabbedPane items={items} />)
expect(tabbedPane.find(Tabs)).to.have.length(1)
...

but… Should I write those tests? Do they provide value? Or might they get in my way later (as in React TDD 2: Value and Cost of Tests)?

Is the code adding the “Tabs” and “Tab” actually “too simple to break”?

What Matters…

Before I talk about “too simple to break”, let’s talk about what I care about here. I care about:

  • That there is a button for each item
  • That each button has the correct text
  • That clicking each button calls the correct callback
  • That it looks nice

I can easily write tests for the first three: That there is a button for each item…

expect(buttons).to.have.lenght(items.length)

That each button has the correct text…

expect(buttons.at(0)).to.have.text(items[0].text)

That clicking each button calls the correct callback…

buttons.at(0).simulate(click)
verify(onClickMock)

What about the fourth point?

I cannot easily test for that. Checking whether there are “Tabs” and “Tab” components will not ensure that it looks nice.

Testing exact CSS style of the “Tabs” and “Tab” components would only lock down my implementation and prevent me from redesigning it later - redesigning it to be even nicer.

Too Simple to Break?

So, writing those tests - the tests for the styled components - is easy but it has a very high potential future cost. But is this functionality “too simple to break”? Remember, I said that this means

  1. The chance of the code breaking is very low
  2. If the code breaks, we would see immediately

The chance of the code breaking is very low

If someone changes how this component uses the “styled components”, it’s almost certainly a redesign of the app.

Remember, they only add CSS. So, a change here will probably not break anything but make the app look nicer.

If the code breaks, we would see immediately

If the code breaks, the item buttons in the GUI would look ugly. We would see it at the first glance. If it is a hidden feature, it might be harder to spot, but once we navigate to it, looking at the gui will tell us.

So, here I have an example where

  • Writing some tests would be very easy but…
  • I consider the code too simple to break and…
  • The tests would not give me confidence that “the app looks nice” anyway.

Another Heuristic

Code that needs to be changed often is, most of the time, NOT too simple to break. When you must go back and change some code that you previously deemed “too simple to break”, think again.

Code that changes frequently usually needs tests. So, try to find a way to write some tests before making that change.