Three things:
(1) I think Aaron did a nice job highlighting some of the differences between elmer and elm-testable – Thanks! Here’s a link to a medium article I wrote that gives another example of test-driving an elm application:
(2) It’s a great suggestion to use elm-html-test for the low-level html stuff in elmer; I will definitely look at that. Reducing the amount of native code in elmer is, of course, something I’m very interested to do.
(3) For spying on functions: Yes, if you start spying on functions during tests, you are starting to expose some implementation details, so you’ll want to do this with care.
It might be helpful here to distinguish two types of details: the ‘inner workings’ of a module and its ‘exposed interface’. Tests will probably need to know about the exposed interfaces of software components, but should try not to know about the inner workings of those components.
For example, when you’re writing ‘end-to-end’ style tests with elmer (edit: not a good use of ‘end-to-end’ here; I mean unit tests that describe the public behavior of a model, view function, and update function in interaction with each other), these tests will need to know about the interfaces of the app’s high-level dependencies – for example, a function that produces a command to make an http request. Usually, you can just spy on and stub functions that produce the relevant commands and subscriptions to simulate the side effects necessary for describing the behavior of your app under specific conditions. So your tests know some details about these interfaces but not about the inner workings of the app.
As an app becomes large, you might also find a need to spy on functions that represent exposed interfaces between different parts of the app. I’m interested in agile architecture design patterns like hexagonal architecture and clean architecture, and I’m a big fan of the SOLID principles. Those kinds of strategies recommend dividing your application into smaller, decoupled components. Once you do that, you might want to divide your tests along similar lines, describing the behavior of each component independently. Elmer’s capacity to spy on and stub functions can be useful when testing such components in isolation.
For example, you might want the UI portion of your app (with its own model, update, view) to be decoupled from the mechanisms by which the application gathers data. This is a good thing, since these two portions of your app will probably change for different reasons, and separating them makes each easier to understand and change. But then, your tests should probably respect this decoupling. You don’t want the tests of your UI to presuppose any particular mechanism for gathering data, since if you change that mechanism you’ll then need to update those tests. So, in the tests of your UI, you might spy on and stub the functions the represent the exposed interface by which data is gathered, and vice-versa. This lets you ensure that each component respects the interface between them and more easily simulate conditions to help you describe specific behaviors of the component under test.
So, yes, to spy on a function during a test you’ll need to know about some implementation choices. If you follow the SOLID principles or other agile architecture patterms, you’ll want to give your code a high-level structure composed of modules that are decoupled and easy to change. In that case, the tests may need to know about the interfaces exposed by those components, but they should know as little as possible about the inner workings of each component. And, I should stress, I think these kinds of patterns are something code needs to adopt only when an application starts to become big. For simple elm apps, just spying to stub commands and subscriptions is enough, and I think elmer lets you do that without needing to know too much about the inner workings of the app.
If there are ways to accomplish the kind of spying that elmer enables without the use of native code, I’d be happy to hear it.