Fl4m3Ph03n1x

Fl4m3Ph03n1x

Background

For those of you wondering what classical TDD is and Mockist TDD is:

Mainly, classical TDD is focused on testing system boundaries and treats your projects like a black box. It provides for flexible tests that don’t break upon implementation changes.
Mockist TDD, tests the interactions between each component of your app, so it is more coupled to implementation details, but it offers stronger/more precise tests.

TDD in Exlir

I am now trying a new approach to testing apps in Elixir. I have read for the 100th time Mocks and explicit contracts in Elixir and I get the idea of creating interfaces and then having implementations for those. This right here is a classical approach to TDD, if I have ever seen one.

Jose Valim even mentions project boundaries and the need to define those, so I take it that in his mind, all we need to test are the boundaries, which is why we make our applications depend on interfaces (at the boundaries).

This is all fine but…

But I have an issue with this. I know some people love REPL development. I also find it fun - for toy projects. In a real enterprise system, you need a strong suite of tests that can be automatically run. REPL doesn’t cut it.

So, this leaves me with a severe deficiency - how do I test that my system is working internally as expected?
If I only test the boundaries, I have no guarantees.

A very simple example of this is using a cache. If I only test the boundaries, like Jose states, then I will never know if the answer to my query is coming from a cache or from a computation, since to the outside world, both are the same.

It also makes it impossible to test if your processes are being registered correctly, or if your GenServers are doing well instead of just dying, being restarted, and re-running the request with a clean state.

An obvious solution?

Perhaps the obvious solution here would be to just add more boundaries. Boundaries everywhere !
But this is non-sense at best. If we follow this approach we will end up with a contract for every component, because we need to test their interactions. This is crazy.

Questions

  1. So why is Elixir so focused on classical testing?
  2. How do you test the interactions within your system?
  3. If you see yourself as a mockist, which tools to you use? Do you use Mox?

Showing Posts 1 to 10

lpil

lpil

Creator of Gleam

The only thing I mock in my application is HTTP calls to external services, and that’s just so I don’t blow the quota or create lots of junk data when running unit tests. Typically I’ll also have tests that hit them for-real that are infrequently run.

I don’t really understand the purpose of mocks in dynamic languages. I think if you care about the code and you don’t have a good type system then you should run the code.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

I take it you don’t use mox :smiley:
Also, I see you are using mocks as a verb and not as a noun. Jose Valim would like to have a word with you :slight_smile:

lpil

lpil

Creator of Gleam

I’m not a fan of either :slight_smile:

Ninigi

Ninigi

I have been using mox pretty much from when Jose released it, and I had a problem with the “verboseness” of the approach in the beginning, before seeing some actual improvements of the code base - usually when you are replacing one API-connector library with another, or something like that. Since you usually end up introducing a layer between the library and your app, all the code interacting with said library is in one module or modules in the same context, not scattered around your whole code base.

That said, I actually do not use mocks very often, pretty much only for API interactions now that I think about it - and in those cases you can write integration tests that do not use the mock API-connector if you prefer.

I haven’t read the article you linked yet, but I am interested and certainly will when I have some more time - but could you elaborate more on what you mean with

If I only test the boundaries, like Jose states, then I will never know if the answer to my query is coming from a cache or from a computation, since to the outside world, both are the same.

I don’t know what kind of System you work with, but in my experience you can use whatever test framework in whatever language you want for integration tests that simulate real user interaction - because they don’t care about the language your system is written in. What or how exactly would you like to test in your system?

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

My assertion is transversal to any system. When you only test a system’s boundaries, you only test what’s coming in and what’s coming out, you treat the system like a black box.

This has several issues (like the cache one, how do you know your cache is really being called?) and this is something I don’t see anyone in Elixir discussing. No books, no resources, no nothing. Everyone has their personal workaround, but I found no standard way of doing anything.

The existing frameworks are rather limited and force you to change production code for the sake of testing, which is something I am not a fan of either.

Also, I believe you are confusing integration tests with end to end tests. I am not simulating users here. Imagine this is a backend server API. A very big one. No users clicking around :smiley:

Ideally, I would like to test the interactions between the components of my system in a standard way, but I find the community is way too divided in that regard to provide a standard solution.

LostKobrakai

LostKobrakai

If you want to go all in on not using integration tests this might be good resource: Beware the Integrated Tests Scam (was Integrated Tests Are a Scam) - The Code Whisperer

It‘s a quite extreme view on testing though, which afaik even the author no longer holds to the extend presented in the talk.

One important piece here is that you never just use a mock and expect that to suffice. You‘ll always need to test any implementations to conform to expected behaviour as well. If both sides of the contract work in isolation it‘s expected to work together as well. You can have a small set of integration tests to be absolutely sure as well. The big difference to not using mocks is that you slice up the places where you test the parts of your system compared to running each part of it for all of your tests.

I‘m wondering what you‘re missing here. I mean the hard part is having a mock implementation replace the actual one. If you want to test the real interactions between parts you shouldn‘t need to do anything special.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

I saw that a few years ago. It made me a true believer of J B Rainsberger :stuck_out_tongue:

If there is a standard way of doing TDD in Elixir, I would love an example. The cache one would be a good starting point.

peerreynders

peerreynders

Dan North has an interesting take on this: Spike and Stabilize.

To minimize opportunity cost:

  • Quickly develop feature (e.g. REPL driven development)
  • Put it into production
    • Delete feature if it isn’t needed
    • Or if the feature proves useful expend the effort to make it resilient by adding tests.

A very simple example of this is using a cache.

A cache is an implementation detail. Functional testing doesn’t care as long as the right value is returned. Caching is tested with non-functional testing - i.e. on average is the value returned within the given time limits.


That link should always go together with this one:

Ninigi

Ninigi

I think I have no idea what you are talking about, so I should probably leave it alone, but I’d really like to understand what you mean.

As I said, I pretty much only use mocks to wrap the (sorry if this is the wrong terminology, but I can’t think of a better word here) communication layer to third party applications. So, for a real life example, when writing applications that need to communicate with the Shopify API, I use the shopify package for their REST API and Neuron for GraphQL - but I have wrappers that make it easier to swap the REST API for GraphQL and vice versa. Those wrappers have mocks I use for tests, because the data they take and return should not change (it sometimes does, but then I need to do something anyways).

Another usage is elixir apps communicating with each other, but I treat that the same as the example given - modules for communication and all they do is send and receive data.
Now, if I want to make sure the communication between the apps works, I write higher level tests to test exactly that: do they communicate with each other in the right way.

Again, I feel like I have no idea what you are talking about and I don’t want to tell you “this is the way you do it”, I am just explaining where I am coming from so you can point out what my misunderstanding is, or that I am actually not even talking about the same thing :wink:

I believe you are confusing integration tests with end to end tests.

Got me there, I was pretty sure my terminology was off there, thanks for the correction :smile:

Consumers then, same thing :slight_smile:

I think I am missing an example here.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

I have used that methodology in other companies (different name, we called it pipeline development. It was a bad name, but I am not the one who suggested it!) to know that the stabilize part where you refactor and add tests never really comes, or comes too late - usually when your head is on fire.

Interesting view as it may be, I am not a fan :stuck_out_tongue:

Yep. I also read that a long time ago :smiley:

@Ninigi when you define an API and create a mock that obeys it, you are doing 2 things:

  1. defining the boundaries of the system
  2. testing the communication that reaches those boundaries

That’s a good thing to do imo.

I make the distinction because End to End testing (aka, E2E) involving users usually requires a whole different set of tools and methods to be effectively tested, like Selenium and such, which I am not going to touch because those are topics on their own :smiley:

Search for TDD in this forum and read any posts created by me, You should get a good idea.

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews