Storefront
Testing von Shopware-Storefront Modulen stellt sich dar wie bei jedem anderen Javascript/Typescript Projekt.
Gesetz dem Falle natürlich, man verwendet keine weiteren dependencies die das Testen mit konventionellen mitteln als Schwierig darstellt (z.B. Vue)
Für das Testing sind natürlich weitere Abhängigkeiten notwendig:
{
"scripts": {
"test": "jest --config jest.config.js --ci",
"test-watch": "jest --config jest.config.js --watch"
},
"devDependencies": {
"@babel/plugin-transform-class-properties": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.23.9",
"@types/bootstrap": "^5.2.7",
"@types/jest": "^29.5.12",
"@types/utf8": "^3.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-junit": "^16.0.0",
"ts-jest": "^29.1.2",
"tslib": "^2.6.2",
"typescript": "^5.3.3"
}
}
Sollte Vue verwendet werden durch die Nutzung von Komponenten aus dem BasePlugin-Storefront, kommen noch weitere Elemente hinzu
{
"devDependencies": {
"node-fetch": "^2.7.0",
"jest-mock-server": "^0.1.0",
"@types/node-fetch": "^3.0.3",
"@vue/test-utils": "^2.4.4",
"flush-promises": "^1.0.2"
}
}
Um Jest mit Typescript und Klassen verwenden zu können, wird Babel entsprechend konfiguriert:
module.exports = {
presets: ["@babel/preset-env"],
plugins: [
"@babel/plugin-transform-class-properties",
"@babel/plugin-transform-runtime",
],
};
Jest muss natürlich auch noch Konfiguriert sein:
const { resolve } = require("path");
process.env.STOREFRONT_PATH =
process.env.STOREFRONT_PATH ||
resolve(
"../../../../../../../vendor/shopware/storefront/Resources/app/storefront"
);
process.env.BASEPLUGIN_PATH =
process.env.BASEPLUGIN_PATH || resolve("../../../../../BasePlugin");
module.exports = {
preset: "ts-jest",
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
testEnvironment: "jsdom",
globals: {
storefrontPath: process.env.STOREFRONT_PATH,
},
coverageThreshold: {
global: {
branches: 50,
functions: 90,
lines: 90,
statements: 90,
},
},
reporters: [
"default",
["jest-junit", { outputDirectory: "reports", outputName: "report.xml" }],
],
collectCoverage: true,
coverageReporters: ["lcov", "text", "clover"],
testRegex: "/test/.*\\.(test|spec)?\\.(ts|tsx)$",
collectCoverageFrom: [
"<rootDir>/src/**/*.ts",
"!<rootDir>/src/**/*.spec.ts",
"!<rootDir>/src/**/*.d.ts",
],
moduleDirectories: ["node_modules"],
moduleNameMapper: {
"^bootstrap(.*)$": `${process.env.STOREFRONT_PATH}/node_modules/bootstrap$1`,
"^src(.*)$": `${process.env.STOREFRONT_PATH}/src/$1`,
"^@LeopardenBasePlugin(.*)$": `${process.env.BASEPLUGIN_PATH}/src/Resources/app/storefront/src$1`,
},
testEnvironmentOptions: {
customExportConditions: ["node", "node-addons"],
},
setupFilesAfterEnv: [
`${process.env.STOREFRONT_PATH}/jest.init.js`,
`${process.env.BASEPLUGIN_PATH}/src/Resources/app/storefront/test/jest.init.ts`,
],
};
Manche Elemente, die von Shopware geliefert werden, werden anhand der initialisierungsdatei von BasePlugin/Storefront schon erledigt. Man kann aber auch in folgender Datei weiteres Anpassen
global.console = {
...console,
// uncomment to ignore a specific log level
// log: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
// error: jest.fn(),
};
Zuletzt muss der Typescript Compiler noch ein paar wenige Einstellungen erhalten, damit zum einen Code-Highlighting in PHP-Storm korrekt funktioniert als auch Jest die korrekten Pfade kennt.
"importHelpers": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"outDir": "./dist/storefront/js",
"noImplicitAny": false,
"allowJs": true,
"baseUrl": ".",
"sourceMap": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"paths": {
"src/*": [
"../../../../../../../vendor/shopware/storefront/Resources/app/storefront/src/*"
],
"vendor/*": [
"../../../../../../../vendor/shopware/storefront/Resources/app/storefront/vendor/*"
],
"@LeopardenBasePlugin/*": [
"../../../../../BasePlugin/src/Resources/app/storefront/src/*"
],
"vue/*": [
"../../../../../BasePlugin/src/Resources/app/storefront/node_modules/vue/*"
]
}
},
"stats": {
"errorDetails": true
},
"files": [
"../../../../../BasePlugin/src/Resources/app/storefront/shims.window.d.ts"
],
"include": [
"./**/*.ts"
],
"exclude": [
"node_modules"
]
}
Sollte das BasePlugin nur als Dependency installiert worden sein, so muss natürlich der Pfad entsprechend auf <project_root>/vendor/leoparden/baseplugin/src/Resources/app/storefront/<file> zeigen
Mit npm install und npm run test-watch kann nun das Testen beginnen.
Ein Beispiel-Test:
import { mount } from "@vue/test-utils";
import { resolve } from "path";
import Pagination from "@LeopardenBasePlugin/lib/Pagination";
import { nextTick } from "vue";
describe("%pluginname%", () => {
let plugin: %pluginname%Plugin, app;
const onResponse = (): Promise<EntityResponse<{id: string, productNumber:string}>> =>
new Promise((resolve) => {
plugin.$emitter.subscribe(
"onResponse",
async () => {
await nextTick();
resolve(<EntityResponse<{id: string, productNumber:string}>>plugin.response);
},
{ once: true }
);
});
const setData = async (): Promise<EntityResponse<{id: string, productNumber:string}>> => {
const waiter = onResponse();
plugin.refresh();
return await waiter;
};
const options = {
url: resolve("./test/fixtures/product.json"),
sorting: {
field: "productNumber",
},
baseUrl: "http://www.test.com",
};
beforeEach(async () => {
window.PluginManager.register(
%pluginname%Plugin.pluginName,
%pluginname%Plugin,
%pluginname%Plugin.selector
);
const element = document.createElement("div");
element.dataset.%pluginname% = "true";
element.dataset.%pluginname%Options = JSON.stringify(options);
document.body.appendChild(element);
const init: Promise<%pluginname%Plugin> = new Promise((resolve) => {
element.addEventListener("onInit", (event: CustomEvent) =>
resolve(event.detail)
);
});
new %pluginname%Plugin(element, options, %pluginname%Plugin.pluginName);
plugin = await init;
plugin.pagination = new Pagination(plugin);
app = mount({
...plugin.appOptions(plugin),
template:
"<div>{{response?.elements}}{{plugin.domain}}<table><tr v-for='product in response?.elements'><td :data-id='product.id'><a :href='plugin.getURL(product)'>{{product.name}}</a></td></tr></table></div>",
});
});
it("should be a vue component", () => {
expect(app.vm).toBeTruthy();
});
it("should load products", async () => {
const response = await setData();
expect(plugin.categories).toBeInstanceOf(Map);
expect(plugin.getURL(plugin.categories.get("1").products[0])).toEqual(
`${options.baseUrl}/${response.elements[0].seoUrls[0].pathInfo}`
);
expect(plugin.getURL(plugin.categories.get("1").products[1])).toEqual(
`${options.baseUrl}/detail/${response.elements[1].id}`
);
});
});
Last modified: 29 February 2024