ZplCore.Testing 1.0.0

dotnet add package ZplCore.Testing --version 1.0.0
                    
NuGet\Install-Package ZplCore.Testing -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ZplCore.Testing" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ZplCore.Testing" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ZplCore.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ZplCore.Testing --version 1.0.0
                    
#r "nuget: ZplCore.Testing, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ZplCore.Testing@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ZplCore.Testing&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ZplCore.Testing&version=1.0.0
                    
Install as a Cake Tool

ZplCore.Testing+ – semantic label tests for .NET

Labels are code. Test them like code. ZplCore.Testing asserts what a ZPL label prints – not only how it looks. It renders the label with the zplCore.com engine, reads every barcode back with two independent decoders and measures every element. When something breaks, the message says which element, where (dots, mm, ZPL line) and links an image with the element outlined.

Label(sscc.zpl).Barcode("SSCC").Decodes("(00)376104250021234560") failed: the decoders read "(00)376104250021234569", expected "(00)376104250021234560".
  label       sscc.zpl: label 1 of 1 · 812 × 1218 dots (101.6 × 152.4 mm) @ 203 dpi
  element     ^BC Code128 "(00)376104250021234569" at x=94 y=800 (11.8 / 100.1 mm), 624 × 260 dots (78.1 × 32.5 mm) · line 17, col 1
  source      ^FO94,800^BCN,260,Y,N,N,D^FD(00)376104250021234569^FS
  scan-proof  Match · the field encodes "(00)376104250021234569"
  decoders    zxing-cpp: "(00)376104250021234569" · ZXing.Net: "(00)376104250021234569"
  image       bin/Debug/net10.0/zplcore-failures/Sscc_label-80bca2c9b5.png

Works with xUnit, NUnit, MSTest and any other runner. The package does not reference a test framework.

Quick start in 3 steps

1. Add the package to your test project

dotnet add package ZplCore.Testing

2. Write a test

using ZplCore.Compliance;
using ZplCore.Testing;

public class ShippingLabelTests
{
    [Fact]
    public void Pallet_label_is_scannable_and_compliant()
    {
        string zpl = File.ReadAllText("labels/pallet.zpl");

        ZplAssert.Label(zpl)
            .HasLabels(1)
            .NoOverlaps()
            .NoClipping()
            .Complies(CompliancePacks.Gs1LogisticLabel)
            .Barcode("SSCC").Decodes("(00)376104250021234569").HasGrade(atLeast: 'B').HasQuietZones()
            .And.Text("RETAIL DC").IsWithinLabel().DoesNotOverlap("SSCC")
            .And.MatchesSnapshot("pallet");
    }
}

3. Run it and approve the snapshot once

dotnet test                                   # first run: fails, writes __snapshots__/pallet.received.png
ZPLCORE_ACCEPT_SNAPSHOTS=1 dotnet test        # or: zplcore snapshot accept tests/__snapshots__
git add tests/__snapshots__/pallet.approved.*  # commit approved PNG + semantic JSON

Add *.received.* and __snapshots__/*.diff.png to .gitignore.

What you can assert

ZplAssert.Label(zpl), ZplAssert.Label(labelModel) and ZplAssert.LabelFile("label.zpl" | "label.json") return a fluent LabelAssertions. The job is rendered once; all assertions share the result.

Assertion Checks
HasLabels(n) the ZPL prints n formats (^PQ copies count once)
Renders(dpi?, failOn) at least one label, no render diagnostics (invalid data, missing font/graphic) – optionally at another resolution
NoOverlaps(toleranceDots) no two elements print on the same dots. Each candidate element is rendered on its own, so font line spacing, frames and ^FR fields are not overlaps
NoClipping() every element lies on the label (^PW/^LL)
Complies(CompliancePacks…) / Complies(pack) compliance rule packs (GS1 logistic, Sunrise 2027, Amazon FBA, UPS, UDI, FedEx, DHL Paket, custom packs)
AllBarcodesDecode() / AllBarcodesHaveGrade('C') scan-proof for every barcode
FitsPrinter(IZplPrinterFit) resolution, print width, label length (PrinterSpec, or any implementation)
FitsPrinter(PrinterProfile) the Printer Twin of a printer model: printhead width and clipped fields, length, resolution, firmware, options, fonts, memory
FieldEquals("FN1", "…") the ^FN field prints this value (also in ^XF recalled formats and with ZplTestOptions.FieldValues)
HasText / DoesNotHaveText / HasBarcodes(n) content checks
MatchesSnapshot(name, tolerance) pixel snapshot + semantic snapshot with an approval workflow
Satisfies(predicate, because) your own rule on the measured ComplianceLabel
OnLabel(i) switch to label i (0-based) of a multi-label job

Barcodes: Barcode(selector) selects one barcode. A selector can be "#0" (index), "^BC" (command), "QR", "DataMatrix" or "GS1-128" (symbology), "SSCC", "GTIN" or "(01)" (GS1 key or AI), or any part of the data. It fails when nothing or more than one barcode matches. To pick one of several matches, pass an index: Barcode("Code128", 1). Decodes(), Decodes("(00)…") (GS1 compared element by element), HasGrade(atLeast: 'B'), HasQuietZones(), HasXDimension(minMm, maxMm), HasBarHeight(mm), IsSymbology(…), IsGs1("00", "…").

Texts: Text("Empfänger") matches the exact text first, then a substring, then a substring ignoring case. HasContent, HasCharHeight(mm).

Layout (texts and barcodes): IsWithinLabel(marginMm), DoesNotOverlap("other"), DoesNotOverlapAnything(), IsAt(xMm, yMm, toleranceMm), IsInside(x, y, w, h), IsAbove("other"), IsBelow("other"). Use .And to go back to the label.

The grade is a digital pre-check modelled on ISO/IEC 15416/15415. It is not an ISO verification. Compliance packs are pre-checks against public specifications, not certifications.

Failure messages and images

Every failure throws ZplAssertionException with:

  • Failures: structured data with Assertion, Summary, Details, LabelIndex, Line/Column and the image paths
  • a message with the label, the element position in dots and mm, the ZPL source excerpt and the measured values
  • ImagePath: the label rendered with the failing element in red and related elements in orange. It is written to zplcore-failures in the test output folder. Change the folder with ZPLCORE_FAILURE_DIR or ZplAssert.Settings.FailureImageDirectory.

Use ZplAssertionScope to collect all failures of a test and throw them together:

using (new ZplAssertionScope())
{
    ZplAssert.Label(zpl).NoOverlaps().NoClipping().AllBarcodesDecode();
}

Snapshots: pixels plus semantics

MatchesSnapshot("name") compares the 1-bit render with __snapshots__/name.approved.png. The folder sits next to the test source file and is also found for deterministic CI builds with mapped /_/ paths. name.approved.json stores every element (kind, command, text/data, position). A failing snapshot therefore says what changed, not only how many pixels:

Label(zpl).MatchesSnapshot("pallet") failed: the render differs from the approved snapshot "pallet" and elements changed.
  pixels    2782 changed (0.281 % of 989016), tolerance 0 (exact)
  regions   x=315 y=180 111 × 25 dots (39.4 / 22.5 mm, 1342 px); …
  changes   text "TO" moved by 0 / +12 dots (0.0 / +1.5 mm): x=40 y=140 → x=40 y=152
            text ^A0 at x=40 y=180: text "RETAIL DC SOUTH, MUNICH" → "RETAIL DC NORTH, BERLIN"
  approved  tests/__snapshots__/pallet.approved.png
  received  tests/__snapshots__/pallet.received.png
  diff      tests/__snapshots__/pallet.diff.png
  • Tolerance: MatchesSnapshot("x", 0.5) (percent), SnapshotTolerance.Pixels(50), .WithShift(1) (ignores ink moved by 1 dot, e.g. font rasterising on other operating systems).
  • An added, removed or changed text or barcode always fails, even when the pixel tolerance would allow it. Turn this off with IgnoreContentChanges.
  • To accept, set ZPLCORE_ACCEPT_SNAPSHOTS=1, set ZplTestOptions.AcceptSnapshots, run zplcore snapshot accept <dir>, or rename .received. to .approved..
  • The diff image shows unchanged ink in grey, removed ink in red, added ink in green and changed regions in blue boxes.

Options

ZplAssert.Label(zpl, new ZplTestOptions
{
    Dpi = 300, WidthMm = 100, HeightMm = 150,           // used when the ZPL has no ^PW / ^LL
    FieldValues = new Dictionary<int, string> { [1] = "ACME GmbH" },
    Robustness = true,                                   // scan-proof robustness simulations
    SnapshotDirectory = "label-snapshots",
});

Set process-wide defaults in ZplAssert.Settings: Dpi, SnapshotDirectory, SnapshotTolerance, WriteFailureImages, FailureImageDirectory and ExceptionFactory.

Test framework adapters (optional)

Any exception fails a test. By default (ZplTestFrameworks.AutoDetect), NUnit gets an AssertionException, so it reports a failure and not an error, and MSTest gets an AssertFailedException. Every other runner, xUnit included, gets a ZplAssertionException. The original exception is always the InnerException. To choose explicitly:

ZplAssert.Settings.ExceptionFactory = ZplTestFrameworks.NUnit;   // or MSTest, None, or your own Func<ZplAssertionException, Exception>

Printer fit and the Printer Twin

FitsPrinter takes any IZplPrinterFit. PrinterSpec checks data-sheet limits:

ZplAssert.Label(zpl).FitsPrinter(new PrinterSpec("ZD421", dpi: 203, printWidthMm: 104, maxLabelLengthMm: 991));

Printer models with firmware, fonts, memory and options (the zplCore Printer Twin) plug in through the same interface. Pass a profile directly, by id or alias (zplcore twin list):

using ZplCore.Twin; // PrinterProfiles, PrinterOption

ZplAssert.Label(zpl).FitsPrinter(PrinterProfiles.Get("ZD421-203"));
ZplAssert.Label(zpl).FitsPrinter(PrinterProfiles.Get("ZT411-300").WithOptions(PrinterOption.Cutter).AsPrinterFit());

PrinterProfileFit runs ZplPrinterTwin.Check and reports its ZPL5xxx diagnostics: errors (a field clipped by the printhead, a missing option the label needs …) fail the assertion, warnings (e.g. ^PW wider than the printhead) only with failOnWarnings: true, and unknown profile facts (ZPL5001) are infos that never fail. The resolution the test renders at counts as the design resolution, so a 300 dpi profile reports a label rendered at 203 dpi (ZPL5104); field values and the default label length come from the test options. You can also wrap your own rules with ZplPrinterFit.From(name, context => issues).

CI: zplcore check

The same checks run without a test project. This is the zplcore CLI and the GitHub Action zplcore/label-check (tools/github-action in the zplCore repository; not yet published on the GitHub Marketplace, planned):

dotnet tool install -g ZplCore.Cli
zplcore check "labels/**/*.zpl" --pack gs1-logistic --verify --min-grade C --layout \
  --snapshots tests/label-snapshots --junit out/junit.xml --sarif out/labels.sarif
Option
--pack ids\|all compliance packs (zplcore comply --list-rules)
--verify, --min-grade C scan-proof, fail below a grade
--layout overlaps (ZT1101) and clipping (ZT1102) as warnings
--printer 203:104[:991] data-sheet printer limits: dpi, print width, max length in mm (ZT1201–ZT1203)
--printer ZD421d-203 a Printer Twin profile by id or alias (ZPL5xxx; zplcore twin list)
--snapshots dir, --update-snapshots, --tolerance 0.1%\|25px, --shift n snapshots per file (name = relative path)
--baseline dir --report-dir dir compare with the base version; write before/after/diff PNGs
--format text\|junit\|sarif\|json\|markdown\|github, -o output (plus --junit, --sarif, --json, --markdown files)
--fail-on error\|warning\|note\|none exit code 1 at this severity (default error)

Exit codes: 0 passed, 1 failed, 2 usage or input error. JUnit XML is valid against the Ant and Surefire schemas. SARIF 2.1.0 contains the compliance (ZPL3xxx), scan-proof (SPxxxx) and check rules (ZTxxxx). With a local tool manifest the command is dotnet zplcore check (alias dotnet zplcore test).

Licence

ZplCore.Testing is part of the commercial zplCore.com SDK. The licence terms of the ZplCore package apply. See zplcore.com or write to support@zplcore.com. Third-party notices: THIRD-PARTY-NOTICES.md.

Trademarks

Zebra, ZPL, ZPL II, EPL and Link-OS are trademarks or registered trademarks of Zebra Technologies Corp. QR Code is a registered trademark of DENSO WAVE INCORPORATED. GS1, GS1 DataBar, GS1 Digital Link, EPC and EPCglobal are trademarks of GS1 AISBL. ESC/POS is a registered trademark of Seiko Epson Corporation, Intelligent Mail a registered trademark of the United States Postal Service and Mailmark a registered trademark of Royal Mail Group Ltd. Other product and company names and logos mentioned are trademarks or registered trademarks of their respective owners. We use these names only to describe compatibility with file formats, printer languages and standards. zplCore.com is an independent product and is not endorsed, sponsored or certified by these companies; compliance packs are pre-checks, not certifications.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 40 10/27/2026