For bytes-only code, pass an in-memory binary. If the code opens a file path, create a temporary file in ExUnit setup and register its removal with on_exit. “Blob fixture” is not a special ExUnit feature; it is ordinary test setup combined with Elixir’s binary and file APIs.
Choose a binary or a file path based on what the code accepts
Test the interface your application actually exposes. A function that receives bytes can usually be tested with a binary value, without filesystem setup. A function that opens a path should receive a real file if the test is meant to cover reading, path handling, or file errors.
As an Amazon Associate I earn from qualifying purchases.
| Fixture | Best fit | Trade-off |
|---|---|---|
| In-memory binary | Testing byte-processing behavior | No file creation or cleanup, but does not exercise file IO |
| Temporary file | Testing code that opens or reads a path | More faithful to file IO, with setup and cleanup to manage |
For binary-processing code, choose cases that match its contract. Depending on the behavior under test, useful inputs may include empty content, embedded zero bytes, non-UTF-8 bytes, or a representative larger payload. A binary can contain arbitrary byte values; that does not mean every application-level API accepts every encoding or size.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Create a per-test file fixture with setup and on_exit
ExUnit runs setup before each test. A setup callback can return a map or keyword list, which ExUnit merges into that test’s context. That makes the path and expected bytes explicit in the test rather than hidden in shared state.
defmodule BlobReaderTest do
use ExUnit.Case, async: true
setup do
path = Path.join(System.tmp_dir!(), "blob-#{System.unique_integer([:positive])}.bin")
bytes = <<0, 1, 2, 255>>
:ok = File.write(path, bytes)
on_exit(fn -> File.rm(path) end)
%{blob_path: path, blob_bytes: bytes}
end
test "reads the fixture bytes", %{blob_path: path, blob_bytes: bytes} do
assert {:ok, ^bytes} = File.read(path)
end
end
This illustrative pattern uses a per-test unique filename to reduce collisions when tests run concurrently. It uses the non-bang File.write/2 and File.rm/1 APIs and matches the expected success result for creation. If writing fails, the match fails the setup; adapt error handling and temporary-path choices to your project and supported platforms. The official ExUnit example uses File.write!/2 and removes the file with File.rm!/1.
Register cleanup as soon as the resource has been created. ExUnit runs an on_exit callback in a separate process, so do not make it depend on mutable state held only by the test process. In the per-test setup pattern above, the path is captured when the callback is registered.
Use the right binary IO API
Elixir’s File documentation says files default to binary mode and points to IO.binread/2 and IO.binwrite/2 for binary IO. Use APIs that match the code under test: File.read/1 is suitable when the application reads a whole file by path, while code that works through an IO device should be tested through its corresponding binary IO operations.
Decide whether setup_all is appropriate
setup_all runs once before the module’s tests, rather than once per test. Use it only when sharing fixture state is intentional and safe. ExUnit runs setup_all in a separate process from the tests, which can affect resource ownership and sharing; per-test setup is a simpler choice when each test needs an independent file.
ExUnit runs registered exit callbacks in a separate process. Callbacks registered in a test or setup run in a blocking fashion after that test and before the next test in the case. A callback registered in setup is therefore suited to removing that test’s fixture before the next test proceeds. ExUnit also documents that a registered on_exit/2 callback will run, while failures in setup or setup_all stop remaining setup callbacks from executing.
Keep concurrent tests from sharing a filename
Do not give parallel tests the same fixed temporary filename: one test could overwrite or remove another test’s fixture. A unique per-test path plus immediate cleanup keeps each file’s lifecycle tied to its test. Elixir v1.18 introduced parameterized ExUnit tests and test groups; parameterized scenarios can run concurrently when async: true is used, and groups allow concurrency across groups while tests in the same group do not run concurrently. Account for shared external state when choosing how to group or isolate scenarios.
Rank #4
For process-based fixtures, ExUnit’s supervised-process helpers address a different cleanup concern: its callbacks documentation says supervised processes are guaranteed to terminate before on_exit callbacks run and recommends start_supervised/2 to ensure processes have fully terminated before the next test. A file still needs explicit filesystem cleanup.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Check version compatibility for your project
The official Elixir documentation page accessed in 2026 marked Elixir v1.20.4 stable and listed Erlang/OTP 27, 28, and 29 as supported. Documentation labels and compatibility change over time, so check the versioned documentation that matches the Elixir and OTP versions used by your project.
Quick Recap
Best Value
- ExUnit.Callbacks documentation, v1.20.4
- Elixir File documentation, v1.20.4
- Elixir v1.18 release announcement
- Official Elixir documentation
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




