testhelper

package
v0.18.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 10 Imported by: 0

README

testhelper

Shared fixture builders for tests. This package removes the boilerplate of building Shopware project and extension trees on disk — temp directories, composer.json / composer.lock rendering, and app manifest.xml rendering — so tests state what the fixture is, not how to write it.

Everything here is behavior-first: builders render deterministic output, omit zero-valued fields, and are covered by this package's own tests. When a fixture can't be expressed (see When to keep raw strings), fall back to a raw string through WriteFile — that escape hatch is part of the design, not a workaround.

Files and directories

WriteFile(t, path, content)

The atom: mkdir-p the parent, write the file, fail the test on error. Replaces the os.MkdirAll + os.WriteFile + error-check triple.

testhelper.WriteFile(t, filepath.Join(dir, ".env"), "DATABASE_URL=mysql://app:secret@db/shop\n")
Project — a Shopware project root

Chainable builder around a fresh temp dir for project-shaped fixtures (root composer.json + lock + vendor + custom/plugins):

p := testhelper.NewProject(t).
    File("composer.json", testhelper.ComposerJSON{
        Name:    "shopware/production",
        Require: map[string]string{"shopware/core": "6.6.10.3"},
    }.String()).
    File("composer.lock", testhelper.ComposerLock(
        testhelper.LockPackage{Name: "shopware/core", Version: "v6.6.10.3"},
    )).
    VendorPackage("swag/demo", testhelper.PluginComposer("swag/demo", "2.0.0", `Swag\Demo\Demo`)).
    CustomPlugin("LocalPlugin", testhelper.PluginComposer("acme/local-plugin", "1.0.0", `Acme\LocalPlugin\LocalPlugin`)).
    Dir("var/cache") // empty directory, no file needed

root := p.Root

File and Dir take slash-separated paths relative to Root. Scenario helpers (a package's setupProject(t)) should stay package-local and compose these calls — this package provides the atoms, not the scenarios.

Standalone extension directories

For a single plugin/bundle/app rooted in its own temp dir (not inside a project):

// The composer content matters to the test: keep it visible at the call site.
dir := testhelper.ExtensionDir(t, testhelper.ComposerJSON{
    Name: "shopware/invalid",
    Type: "invalid", // the test asserts this is rejected
})

// The manifest content matters:
appPath := testhelper.AppDir(t, manifest) // manifest is a testhelper.AppManifest

// Any valid extension will do — the test doesn't assert its metadata:
pluginDir := testhelper.NewPlugin(t, "FroshTools") // test/frosh-tools, class FroshTools\FroshTools, v1.0.0
appDir := testhelper.NewApp(t, "MyExampleApp")

Rule of thumb: if the test asserts names, versions, classes, or labels — or the fixture is deliberately broken — use ExtensionDir/AppDir with an explicit builder so the reader sees the arrange step. Use NewPlugin/NewApp only when the fixture is incidental.

composer.json

ComposerJSON renders a manifest; zero-valued fields are omitted, so partial and degenerate manifests ({}, version-only) stay expressible:

testhelper.ComposerJSON{Name: "swag/demo", Type: "shopware-bundle"}.String()

Supported fields: Name, Type, Version, License, Description, Authors (rendered as [{"name": ...}]), Require, RequireDev, PluginClass (extra.shopware-plugin-class), Label (extra.label), Psr4 (autoload.psr-4), and Extra map[string]any for anything else under extra (e.g. shopware-bundle-name, manufacturerLink).

PluginComposer(name, version, class) is the canonical platform plugin: type: shopware-platform-plugin, require: {"shopware/core": "~6.6.0"}, with the en-GB label and PSR-4 prefix derived from the bootstrap class. Override fields on the returned value when the defaults don't fit:

c := testhelper.PluginComposer("frosh/tools", "2.0.0", `Frosh\Tools\FroshTools`)
c.Require = map[string]string{"shopware/core": "~6.7.0"}

An empty version renders no version key (useful for fallback-version tests).

composer.lock

ComposerLock(packages...) renders {"packages": [...], "packages-dev": []}:

testhelper.ComposerLock() // empty lock
testhelper.ComposerLock(
    testhelper.LockPackage{Name: "shopware/core", Version: "v6.6.10.3",
        Require: map[string]string{"php": ">=8.2"}},
    testhelper.LockPackage{Name: "swag/demo", Version: "2.0.0", Type: "shopware-platform-plugin"},
)

LockPackage carries Name, Version, Type, and a per-package Require. Locks needing more (per-package license arrays, populated packages-dev) stay raw — see below.

App manifest.xml

NewAppManifest(name) returns a complete, valid manifest with placeholder metadata (label/description in en + de-DE, author, copyright, version 1.0.0, MIT). Zero-valued fields are omitted, so validator tests express "manifest missing exactly one element" by clearing that field:

m := testhelper.NewAppManifest("MyExampleApp")
m.License = ""          // missing-license variant
m.Compatibility = "~6.5.0"
m.Icon = "app.png"
m.SetupSecret = "foo"   // adds a <setup><secret> block
testhelper.WriteFile(t, filepath.Join(dir, "manifest.xml"), m.String())
// or: dir := testhelper.AppDir(t, m)

Label and Description are map[lang]text with "" as the default language, rendered default-first.

When to keep raw strings

Do not force a fixture through a builder when:

  • The test asserts byte-exact file content (e.g. content compared after a git archive round-trip, or a renderer asserted to leave a file untouched). A builder's formatting is not part of its contract.
  • The fixture is the test data: broken JSON, "{}", partial documents exercising parse-error paths.
  • The shape is intentionally inexpressible: manifests with arbitrary unknown XML elements asserted to survive rewriting, locks with per-package license arrays, composer files with repositories blocks.
  • The file needs special permissions (executables, 0600 credentials) — WriteFile always writes 0o644.

In those cases write the raw string via testhelper.WriteFile (or plain os.WriteFile for special permissions) and, where it isn't obvious, leave a short comment saying why the builder doesn't apply.

Config YAML (.config/shopware-extension.yml, .config/shopware-project.yml) also stays raw — as backtick multiline literals, not \n-escaped strings. Do NOT build these by marshaling the production config structs: their omitempty tags drop zero-valued nested structs, so an explicit enabled: false marshals to nothing and the loader's enabled: true defaults silently flip the fixture's meaning (verified — a Config with zip packing disabled marshals to {}).

Adding to this package

Add a field or helper only when a second (ideally third) call site needs it — single-use shapes stay raw at their call site. Every addition needs coverage in this package's tests, and rendering must stay deterministic (sort map keys before emitting). Test doubles for interfaces (executors, validation checks) do not belong here yet; they live package-local.

Documentation

Overview

Package testhelper provides shared fixture builders for tests: writing files into temp directories and rendering the composer.json / composer.lock shapes that Shopware project and extension tests need over and over.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AppDir

func AppDir(tb testing.TB, m AppManifest) string

AppDir writes manifest.xml into a fresh temp directory and returns the directory: a standalone Shopware app root.

func ComposerLock

func ComposerLock(packages ...LockPackage) string

ComposerLock renders a composer.lock with the given packages and an empty packages-dev section.

func ExtensionDir

func ExtensionDir(tb testing.TB, c ComposerJSON) string

ExtensionDir writes composer.json into a fresh temp directory and returns the directory: a standalone plugin or bundle root. Use it when the test cares about the composer content — the manifest stays visible at the call site; reach for NewPlugin when any valid plugin will do.

func NewApp

func NewApp(tb testing.TB, name string) string

NewApp returns a directory containing a valid app manifest for tests that just need one and don't assert its metadata.

func NewPlugin

func NewPlugin(tb testing.TB, name string) string

NewPlugin returns a directory containing a valid platform plugin for tests that just need one and don't assert its metadata. The composer name and bootstrap class are derived from name: "FroshTools" becomes "test/frosh-tools" with class `FroshTools\FroshTools` at version 1.0.0. Tests that assert those values should use ExtensionDir with an explicit ComposerJSON instead.

func WriteFile

func WriteFile(tb testing.TB, filePath, content string)

WriteFile writes content to path, creating parent directories as needed.

Types

type AppManifest

type AppManifest struct {
	Name          string
	Label         map[string]string // lang -> text; "" is the default language
	Description   map[string]string // lang -> text; "" is the default language
	Compatibility string
	Author        string
	Copyright     string
	Version       string
	License       string
	Icon          string
	SetupSecret   string
}

AppManifest describes a manifest.xml fixture for a Shopware app. Zero-valued fields are omitted from the output, so validator tests can express a manifest missing exactly one required element by clearing that field on the value NewAppManifest returns.

func NewAppManifest

func NewAppManifest(name string) AppManifest

NewAppManifest returns a complete, valid app manifest with placeholder metadata. Tests clear or override individual fields to build variants.

func (AppManifest) String

func (m AppManifest) String() string

String renders the manifest as XML.

type ComposerJSON

type ComposerJSON struct {
	Name         string
	Type         string
	Version      string
	License      string
	Description  string
	Authors      []string // rendered as [{"name": ...}, ...]
	Require      map[string]string
	RequireDev   map[string]string
	PluginClass  string            // extra.shopware-plugin-class
	Label        map[string]string // extra.label
	Extra        map[string]any    // additional extra entries, merged last
	Psr4         map[string]string // autoload.psr-4
	Repositories []map[string]any  // composer repositories, e.g. path or composer type
}

ComposerJSON describes a composer.json fixture. Zero-valued fields are left out of the rendered JSON, so partial and degenerate manifests (only a version, an empty object) stay expressible.

func PluginComposer

func PluginComposer(name, version, class string) ComposerJSON

PluginComposer returns the composer.json for a Shopware platform plugin. class is the fully qualified bootstrap class (e.g. `Swag\Demo\Demo`); the en-GB label and the PSR-4 autoload prefix are derived from it. Callers can override any field before rendering.

func (ComposerJSON) String

func (c ComposerJSON) String() string

String renders the manifest as JSON.

type LockPackage

type LockPackage struct {
	Name    string            `json:"name"`
	Version string            `json:"version"`
	Type    string            `json:"type,omitempty"`
	Require map[string]string `json:"require,omitempty"`
	Dist    map[string]string `json:"dist,omitempty"` // e.g. {"type": "path", "url": "custom/static-plugins/Foo"}
}

LockPackage is one entry in a composer.lock packages list.

type Project

type Project struct {
	TB   testing.TB
	Root string
}

Project is a Shopware project rooted in a temp directory that tests can populate file by file.

func NewProject

func NewProject(tb testing.TB) *Project

NewProject creates an empty project in a fresh temp directory.

func (*Project) CustomPlugin

func (p *Project) CustomPlugin(dirName string, composer ComposerJSON) *Project

CustomPlugin writes custom/plugins/<dirName>/composer.json for a locally installed plugin.

func (*Project) Dir

func (p *Project) Dir(rel string) *Project

Dir creates an empty directory at the slash-separated path relative to the project root.

func (*Project) File

func (p *Project) File(rel, content string) *Project

File writes content at the slash-separated path relative to the project root.

func (*Project) VendorPackage

func (p *Project) VendorPackage(name string, composer ComposerJSON) *Project

VendorPackage writes vendor/<name>/composer.json for a Composer-managed package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL