Skill Tracer
Skills › Scientific Agent Skills › nextflow › testing.md

testing.md

209 lines · skills/nextflow/references/testing.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Testing with nf-test
2
3Targets **nf-test 0.9.5**, Nextflow **26.04.6**, and nf-core tools **4.1.0**. Sources: [nf-test test CLI](https://www.nf-test.com/docs/cli/test/), [assertions](https://www.nf-test.com/docs/assertions/assertions/), and [nf-core module testing](https://nf-co.re/docs/specifications/components/modules/testing). Installed-module/BAM-plugin examples below are illustrative and require their pinned module, tool, plugin and fixture. A tiny two-read BAM test exercised the teaching wrapper from `developing.md` with local SAMtools 1.24, including real sorting, stub versions, and snapshots.
4

Testing with nf-test

Targets nf-test 0.9.5, Nextflow 26.04.6, and nf-core tools 4.1.0. Sources: nf-test test CLI, assertions, and nf-core module testing. Installed-module/BAM-plugin examples below are illustrative and require their pinned module, tool, plugin and fixture. A tiny two-read BAM test exercised the teaching wrapper from developing.md with local SAMtools 1.24, including real sorting, stub versions, and snapshots.

Raw lines 1 to 4
5## Table of Contents
6
7- [Setup](#setup)
8- [Test file structure](#test-file-structure)
9- [Testing a module (process)](#testing-a-module-process)
10- [Assertions](#assertions)
11- [Snapshot testing](#snapshot-testing)
12- [Testing workflows and pipelines](#testing-workflows-and-pipelines)
13- [Running tests](#running-tests)
14- [nf-core integration](#nf-core-integration)

Table of Contents

  • Setup
  • Test file structure
  • Testing a module (process)
  • Assertions
  • Snapshot testing
  • Testing workflows and pipelines
  • Running tests
  • nf-core integration
Raw lines 5 to 15
16## Setup
18Installation alternatives below are illustrative; the tested runtime was the
19pinned release archive. The official [installer](https://www.nf-test.com/installation/)
20takes the release number as its first argument.
22```bash
23# Option 1: Conda
24conda install -c conda-forge -c bioconda nf-test=0.9.5
26# Option 2: upstream installer
27curl -fsSL https://get.nf-test.com -o install-nf-test.sh
28# Review the installer before executing it.
29bash install-nf-test.sh 0.9.5
30mkdir -p "$HOME/.local/bin"
31mv nf-test "$HOME/.local/bin/"
32export PATH="$HOME/.local/bin:$PATH"
33nf-test version
35nf-test init # creates nf-test.config + tests/ scaffolding in a project
36```
38Test files end in `.nf.test` and live next to the component (`tests/main.nf.test`). Expected results are stored in a sibling `.nf.test.snap` snapshot file.

Setup

Installation alternatives below are illustrative; the tested runtime was the pinned release archive. The official installer takes the release number as its first argument.

# Option 1: Conda
conda install -c conda-forge -c bioconda nf-test=0.9.5

# Option 2: upstream installer
curl -fsSL https://get.nf-test.com -o install-nf-test.sh
# Review the installer before executing it.
bash install-nf-test.sh 0.9.5
mkdir -p "$HOME/.local/bin"
mv nf-test "$HOME/.local/bin/"
export PATH="$HOME/.local/bin:$PATH"
nf-test version

nf-test init        # creates nf-test.config + tests/ scaffolding in a project

Test files end in .nf.test and live next to the component (tests/main.nf.test). Expected results are stored in a sibling .nf.test.snap snapshot file.

Raw lines 16 to 39
Runs code bash install-nf-test.sh :29
40## Test file structure
42Choose the scope matching what you test:
44- `nextflow_process` — a single process/module
45- `nextflow_workflow` — a (sub)workflow
46- `nextflow_pipeline` — a whole pipeline (`main.nf`)
47- `nextflow_function` is also available for testing a function.
49Common layout:
51```groovy
52nextflow_process {
54 name "Test SAMTOOLS_SORT"
55 script "../main.nf" // the module under test
56 process "SAMTOOLS_SORT"
58 tag "modules"
59 tag "modules_nfcore"
60 tag "samtools"
61 tag "samtools/sort"
63 test("sarscov2 - bam") {
64 when {
65 process {
66 """
67 input[0] = [
68 [ id:'test', single_end:false ],
69 file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true)
70 ]
71 input[1] = [[:], [], []] // BAM input: no reference needed
72 input[2] = '' // do not request an inline index
73 """
74 }
75 }
76 then {
77 assertAll(
78 { assert process.success },
79 { assert snapshot(process.out).match() }
80 )
81 }
82 }
84```
86- `input[0]`, `input[1]`, … bind positional process inputs. The current `SAMTOOLS_SORT` example above has three inputs; a copied older module can differ. Always match the installed `main.nf`/`meta.yml`.
87- A `setup { }` block can run prerequisite processes to produce inputs.
88- A `params { }` block sets parameters; a `config "..."` line loads a config for the test.
89- nf-core requires a **stub test** alongside the real one — add `options "-stub"` inside a `test(...)` block to exercise the `stub:` script.

Test file structure

Choose the scope matching what you test:

  • nextflow_process — a single process/module
  • nextflow_workflow — a (sub)workflow
  • nextflow_pipeline — a whole pipeline (main.nf)
  • nextflow_function is also available for testing a function.

Common layout:

nextflow_process {

    name "Test SAMTOOLS_SORT"
    script "../main.nf"          // the module under test
    process "SAMTOOLS_SORT"

    tag "modules"
    tag "modules_nfcore"
    tag "samtools"
    tag "samtools/sort"

    test("sarscov2 - bam") {
        when {
            process {
                """
                input[0] = [
                    [ id:'test', single_end:false ],
                    file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true)
                ]
                input[1] = [[:], [], []] // BAM input: no reference needed
                input[2] = ''            // do not request an inline index
                """
            }
        }
        then {
            assertAll(
                { assert process.success },
                { assert snapshot(process.out).match() }
            )
        }
    }
}
  • input[0], input[1], … bind positional process inputs. The current SAMTOOLS_SORT example above has three inputs; a copied older module can differ. Always match the installed main.nf/meta.yml.
  • A setup { } block can run prerequisite processes to produce inputs.
  • A params { } block sets parameters; a config "..." line loads a config for the test.
  • nf-core requires a stub test alongside the real one — add options "-stub" inside a test(...) block to exercise the stub: script.
Raw lines 40 to 90
91## Testing a module (process)
93The `when` block supplies inputs; the `then` block asserts on results. Use a `setup` block when a module needs another module's output first (the following test belongs in a `nextflow_process` scope targeting `SAMTOOLS_INDEX`):
95```groovy
96test("sort then index") {
97 setup {
98 run("SAMTOOLS_SORT") {
99 script "../../sort/main.nf"
100 process {
101 """
102 input[0] = [ [id:'test'], file(params.test_data + 'test.bam', checkIfExists:true) ]
103 input[1] = [[:], [], []]
104 input[2] = ''
105 """
106 }
107 }
108 }
109 when {
110 process {
111 """
112 input[0] = SAMTOOLS_SORT.out.bam
113 """
114 }
115 }
116 then {
117 assert process.success
118 assert snapshot(process.out).match()
119 }
121```

Testing a module (process)

The when block supplies inputs; the then block asserts on results. Use a setup block when a module needs another module's output first (the following test belongs in a nextflow_process scope targeting SAMTOOLS_INDEX):

test("sort then index") {
    setup {
        run("SAMTOOLS_SORT") {
            script "../../sort/main.nf"
            process {
                """
                input[0] = [ [id:'test'], file(params.test_data + 'test.bam', checkIfExists:true) ]
                input[1] = [[:], [], []]
                input[2] = ''
                """
            }
        }
    }
    when {
        process {
            """
            input[0] = SAMTOOLS_SORT.out.bam
            """
        }
    }
    then {
        assert process.success
        assert snapshot(process.out).match()
    }
}
Raw lines 91 to 122
123## Assertions
125Inside `then`, wrap multiple checks in `assertAll(...)` so all failures are reported at once. Useful handles and helpers:
127| Expression | Checks |
128|------------|--------|
129| `process.success` / `process.failed` | Test workflow run succeeded / failed (not an individual task’s scientific validity) |
130| `process.exitStatus == 0` | Nextflow invocation exit code |
131| `process.out.<emit>` | A named output channel's contents |
132| `process.out.bam.get(0)` | First emitted item |
133| `workflow.success`, `workflow.trace.tasks().size()` | Workflow outcome / task count |
134| `path(process.out.bam[0][1]).exists()` | A file exists |
135| `snapshot(...).match()` | Compare to stored snapshot |
136| `assertContainsInAnyOrder(ch, [...])` | A channel contains the given items (order-agnostic) |
138> There is **no** `assertContainsInOrder`. For ordered or substring checks on file contents, read the lines and assert directly, e.g. `assert path(out[0][1]).readLines().any { it.contains('Done') }` or `assert path(out[0][1]).readLines().last().contains('completed')`.
140nf-test sorts captured channel values for assertions; indexed assertions do not prove task completion order. In 0.9.5, trace `succeeded()` counts `COMPLETED` entries, while cached entries are not classified as succeeded; do not interpret this helper as a universal cache-aware success check.
142Plugins extend assertions for domain files (e.g. `nft-bam` for BAM, `nft-vcf` for VCF, `nft-utils`); nf-core enables these in `nf-test.config`. Pin the plugin version; the [nft-bam API](https://github.com/nvnieuwk/nft-bam/blob/main/docs/usage.md) defines the checksum helper used below. Example with a file-content assertion for a topic-based module:
144```groovy
145then {
146 assertAll(
147 { assert process.success },
148 { assert path(process.out.bam[0][1]).exists() },
149 { assert snapshot(
150 bam(process.out.bam[0][1]).getSamLinesMD5(),
151 process.out.versions_samtools
152 ).match() }
153 )
155```

Assertions

Inside then, wrap multiple checks in assertAll(...) so all failures are reported at once. Useful handles and helpers:

ExpressionChecks
process.success / process.failedTest workflow run succeeded / failed (not an individual task’s scientific validity)
process.exitStatus == 0Nextflow invocation exit code
process.out.<emit>A named output channel's contents
process.out.bam.get(0)First emitted item
workflow.success, workflow.trace.tasks().size()Workflow outcome / task count
path(process.out.bam[0][1]).exists()A file exists
snapshot(...).match()Compare to stored snapshot
assertContainsInAnyOrder(ch, [...])A channel contains the given items (order-agnostic)
There is no assertContainsInOrder. For ordered or substring checks on file contents, read the lines and assert directly, e.g. assert path(out[0][1]).readLines().any { it.contains('Done') } or assert path(out[0][1]).readLines().last().contains('completed').

nf-test sorts captured channel values for assertions; indexed assertions do not prove task completion order. In 0.9.5, trace succeeded() counts COMPLETED entries, while cached entries are not classified as succeeded; do not interpret this helper as a universal cache-aware success check.

Plugins extend assertions for domain files (e.g. nft-bam for BAM, nft-vcf for VCF, nft-utils); nf-core enables these in nf-test.config. Pin the plugin version; the nft-bam API defines the checksum helper used below. Example with a file-content assertion for a topic-based module:

then {
    assertAll(
        { assert process.success },
        { assert path(process.out.bam[0][1]).exists() },
        { assert snapshot(
            bam(process.out.bam[0][1]).getSamLinesMD5(),
            process.out.versions_samtools
          ).match() }
    )
}
Raw lines 123 to 156
157## Snapshot testing
159`snapshot(x).match()` serializes `x` and compares it to the `.nf.test.snap` file. The **first** run records the snapshot; later runs fail if output changes.
161- Snapshot stable things: deterministic file content/checksums, version tuples (or legacy YAML), and cardinalities. Verify records/sample identities and numeric tolerances separately. A new snapshot can preserve a wrong or empty result; review it against an independent expected answer.
162- Stub output/success only tests wiring. A real tiny biological fixture with expected counts, coordinates, units and reference provenance is necessary to assess scientific behavior.
163- Regenerate intentionally-changed snapshots with `nf-test test --update-snapshot`.
164- Name multiple snapshots in one test with `.match("bam")`, `.match("versions")`.

Snapshot testing

snapshot(x).match() serializes x and compares it to the .nf.test.snap file. The first run records the snapshot; later runs fail if output changes.

  • Snapshot stable things: deterministic file content/checksums, version tuples (or legacy YAML), and cardinalities. Verify records/sample identities and numeric tolerances separately. A new snapshot can preserve a wrong or empty result; review it against an independent expected answer.
  • Stub output/success only tests wiring. A real tiny biological fixture with expected counts, coordinates, units and reference provenance is necessary to assess scientific behavior.
  • Regenerate intentionally-changed snapshots with nf-test test --update-snapshot.
  • Name multiple snapshots in one test with .match("bam"), .match("versions").
Raw lines 157 to 165
166## Testing workflows and pipelines
168```groovy
169nextflow_pipeline {
170 name "Test full pipeline"
171 script "../main.nf"
172 test("default params") {
173 when { params { outdir = "$outputDir"; input = "tests/samplesheet.csv" } }
174 then {
175 assert workflow.success
176 assert workflow.trace.succeeded().size() > 0
177 }
178 }
180```
182Use tiny inputs from nf-core/test-datasets (`nf-core test-datasets search ...`) so tests stay fast.

Testing workflows and pipelines

nextflow_pipeline {
    name "Test full pipeline"
    script "../main.nf"
    test("default params") {
        when { params { outdir = "$outputDir"; input = "tests/samplesheet.csv" } }
        then {
            assert workflow.success
            assert workflow.trace.succeeded().size() > 0
        }
    }
}

Use tiny inputs from nf-core/test-datasets (nf-core test-datasets search ...) so tests stay fast.

Raw lines 166 to 183
184## Running tests
186```bash
187nf-test test # run everything
188nf-test test modules/nf-core/samtools/sort/ # a directory
189nf-test test tests/main.nf.test # one file
190nf-test test --tag samtools # by tag
191nf-test test --profile docker # choose container engine
192nf-test test --update-snapshot # accept new snapshots
193nf-test test --changed-since HEAD^ # only components changed since a ref
194nf-test test --only-changed --ci # select current worktree changes; missing snapshots fail
195```
197`nf-test.config` sets the test directory, default profile, and plugins. CI typically runs only changed components via `--changed-since` plus the nf-core `nf-test` GitHub Action.

Running tests

nf-test test                                  # run everything
nf-test test modules/nf-core/samtools/sort/   # a directory
nf-test test tests/main.nf.test               # one file
nf-test test --tag samtools                    # by tag
nf-test test --profile docker                  # choose container engine
nf-test test --update-snapshot                 # accept new snapshots
nf-test test --changed-since HEAD^             # only components changed since a ref
nf-test test --only-changed --ci               # select current worktree changes; missing snapshots fail

nf-test.config sets the test directory, default profile, and plugins. CI typically runs only changed components via --changed-since plus the nf-core nf-test GitHub Action.

Raw lines 184 to 198
199## nf-core integration
201In nf-core/modules-like repositories, the wrapper runs tests twice to check snapshot stability (`--once` skips this). It does not work for modified modules inside pipeline repositories; use `nf-test test <path>` there. `create` scaffolds the test files:
203```bash
204nf-core modules create mytool # scaffolds tests/main.nf.test
205nf-core modules test mytool # runs the module's nf-test suite
206nf-core subworkflows test mysubwf
207```
209Select supported profiles explicitly and review generated snapshots rather than automatically accepting updates. Every nf-core module/subworkflow must ship passing nf-test tests (including a stub test) with committed snapshots; `nf-core modules lint` checks their presence. See `references/developing.md`.

nf-core integration

In nf-core/modules-like repositories, the wrapper runs tests twice to check snapshot stability (--once skips this). It does not work for modified modules inside pipeline repositories; use nf-test test <path> there. create scaffolds the test files:

nf-core modules create mytool         # scaffolds tests/main.nf.test
nf-core modules test mytool            # runs the module's nf-test suite
nf-core subworkflows test mysubwf

Select supported profiles explicitly and review generated snapshots rather than automatically accepting updates. Every nf-core module/subworkflow must ship passing nf-test tests (including a stub test) with committed snapshots; nf-core modules lint checks their presence. See references/developing.md.

Raw lines 199 to 209