CodeGym /Kurse /Spring Test /Upload von Anhängen mit multipart

Upload von Anhängen mit multipart

Spring Test
Level 13, Lektion 1
Verfügbar

1. Upload als HTTP-Vertrag

Das Hochladen einer Datei in eine Backend-Anwendung wird oft als „jetzt wird irgendwo dort auf dem Server eine Datei erscheinen, und wenn sie erscheint, ist alles gut“ wahrgenommen. Das ist ein Ansatz aus der Reihe „funktioniert – nicht anfassen“, der beim Testen fast immer zu Überraschungen führt. Auf der Controller-Ebene interessiert uns etwas anderes: wie der Client die Datei senden soll und was er genau zurückbekommt, einschließlich der Fehlerszenarien.

In unserem Projekt ContentHub ist der Upload von Anhängen ein Editor-Endpoint POST /api/editor/articles/{id}/attachments. Der Client sendet eine Datei, und die API antwortet mit Metadaten: Originalname, Größe, Content-Type, Anhang-ID (und manchmal einem Download-Link). Auf dieser Ebene müssen wir nicht (und sollten nicht) überprüfen, ob die Datei tatsächlich auf das Dateisystem geschrieben wurde. Das ist Aufgabe einer anderen Testebene. Unser Ziel ist jetzt einfacher und ehrlicher: sicherzustellen, dass sich die HTTP-Schnittstelle vorhersagbar verhält.

Um Verwechslungen zu vermeiden, kann man sich eine einfache „Verantwortlichkeitstabelle“ vorstellen (das ist nützlicher, als 12 Anmerkungen auswendig zu lernen):

Was wir im MVC-Test des Uploads prüfen Warum das Web-Layer ist Was wir nicht prüfen Warum das nicht Web-Layer ist
richtige URI und id des Artikels Mapping des Controllers echtes Schreiben auf die Platte das ist Storage/Integration-Zone
Name des Multipart-Feldes (z.B. "file") Vertrag der Anfrage echter Speicherpfad (/data/...) technische Details des Adapters
Content-Type der Datei und grundlegende Beschränkungen Eingabeformat Algorithmus zur Erzeugung von storedFilename interne Logik des Dienstes
Antwortstatus und JSON-Payload externer Vertrag wie viele Anhänge der Artikel tatsächlich in der DB hat Data/Service-Layer
Format des Fehlers ApiProblem Fehlervertrag Sicherheit/Rollen/Owner-Check separater Security-Block

Wenn man diese Trennung beibehält, verliert der Upload-Endpoint seine „unheimliche mystische Aura“ und wird zu einem weiteren HTTP-Szenario.

2. Was ist Multipart in einer Anfrage

Multipart erscheint vielen kompliziert, weil es selten mit eigenen Augen gelesen wird. Bei JSON ist alles klar: geschweifte Klammern, Felder, Werte. Bei Multipart ist der Körper der Anfrage ein „Brief mit mehreren Anhängen“: ein Teil ist eine Datei, ein anderer Teil möglicherweise einige Formularfelder, und alles ist in einer Anfrage mit Grenzen (Boundary) verpackt. Tatsächlich ist es nur ein Verpackungsformat, keine separate Religion.

Ganz grob kann man eine HTTP-Upload-Anfrage so darstellen (stark vereinfacht):

POST /api/editor/articles/10/attachments HTTP/1.1
Content-Type: multipart/form-data; boundary=----abc123

------abc123
Content-Disposition: form-data; name="file"; filename="cover.png"
Content-Type: image/png

<Dateibytes>
------abc123--

Spring MVC „schluckt“ dieses Format mit dem Multipart-Mechanismus (in Boot wird er normalerweise automatisch aktiviert). Danach arbeitet Ihr Controller nicht mehr mit Boundaries und rohen Zeichenketten. Er erhält ein MultipartFile-Objekt mit ganz menschlichen Methoden: getOriginalFilename(), getContentType(), getSize(), isEmpty().

Um die Anfrage-Route innerhalb von MVC einfacher darzustellen, ist ein einfaches Schema nützlich:

flowchart TD
    A[HTTP multipart/form-data request] --> B[Multipart parsing / resolver]
    B --> C[Controller method receives MultipartFile]
    C --> D[Service: validate + store + save metadata]
    D --> E[Controller returns JSON response DTO]
    C -->|exception| F["@ControllerAdvice -> ApiProblem"]

Für Tests bedeutet das eine wichtige Sache: wir müssen keine echten Dateien auf der Platte erstellen, um die HTTP-Grenze zu testen. Wir erstellen MockMultipartFile (im Grunde Bytes im Speicher + Metadaten), senden es über MockMvc und prüfen, ob der Controller den „Brief“ korrekt gelesen hat und entweder Erfolg zurückgibt oder einen Fehler sauber formatiert.

3. Minimales Gerüst eines Upload-Endpoints

Bevor wir Multipart testen, ist es sinnvoll, die minimale Form dessen zu vereinbaren, „was der Controller überhaupt tut“. Es ist wichtig zu sehen, welche Parameter die Methode akzeptiert, an wen sie delegiert und welche Antwort sie erzeugt. Ohne das wird der Test wie Kaffeesatzlesen: „Ich habe eine Datei gesendet, es hat irgendwas geantwortet … wahrscheinlich richtig?“

Stellen wir uns eine vereinfachte Version vor (eine Idee, nicht die „einzig wahre Implementierung“). Der Controller akzeptiert die id des Artikels und ein Multipart-Feld file, delegiert dann an einen Service, der ein DTO der Anhang-Metadaten zurückgibt:

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
class EditorArticleController {

    // Service, der die Businesslogik des Uploads beherbergt (Validierung, Speicherung, Metadaten)
    private final ArticleAttachmentService attachmentService;

    EditorArticleController(ArticleAttachmentService attachmentService) {
        // Abhängigkeit per Konstruktor injizieren – praktisch für Tests und transparent im Vertrag
        this.attachmentService = attachmentService;
    }

    @PostMapping(
            value = "/api/editor/articles/{id}/attachments",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE // Explizit: wir erwarten multipart/form-data
    )
    ArticleAttachmentResponse upload(@PathVariable long id,
                                     @RequestPart("file") MultipartFile file) {
        // Der Controller „speichert die Datei nicht“, er akzeptiert nur den Part und delegiert weiter
        return attachmentService.upload(id, file);
    }
}

Im Happy Path sieht man hier das Wesentliche: Der Controller akzeptiert MultipartFile und delegiert weiter. Aber allein mit @RequestPart wird ein leerer Upload nicht automatisch zu einem korrekten 400-Fehler. Wenn das Projekt einen leeren Datei-Upload als Fehler der Web-Grenze betrachtet, braucht es neben dem Controller einen expliziten Guard oder Validator auf file.isEmpty(), der die Anfrage vor dem Aufruf des Services abfängt. Auf diese Variante stützt sich der unten stehende Test mit verifyNoInteractions(...).

Die Antwort halten wir ebenfalls einfach: das, was für den Client wirklich nützlich ist, um es zu prüfen und anzuzeigen.

public record ArticleAttachmentResponse(
        long id,                 // Identifikator des Anhangs (wird vom Client z.B. zum Löschen/Herunterladen verwendet)
        String originalFilename, // Vom Client gesendeter Name
        String contentType,      // MIME-Typ der Datei laut Multipart-Daten
        long size                // Größe in Bytes
) {}

Fehler werden, wie schon zuvor, von unserem @ControllerAdvice in ApiProblem „übersetzt“. Und hier beginnt das Interessante: Der Upload hat mehrere Arten von Fehlschlägen, die wichtig voneinander zu unterscheiden sind, weil es für den Client unterschiedliche Ursachen sind und für uns unterschiedliche Tests.

4. Gerüst eines @WebMvcTest für Multipart

Ein MVC-Slice mit Multipart sieht fast genauso aus wie ein gewöhnlicher Controller-Test. Der Unterschied besteht nur darin, dass wir statt .content("{json}") einen Multipart-Builder und MockMultipartFile verwenden. Wichtig ist, nicht zu vergessen: Der Slice sollte schmal bleiben. Keine Dateisysteme, keine Datenbank, kein „ach, lass mich mal eben alles wirklich speichern“. Das ist ein Kurs über das Testen von Schichten, kein Wettbewerb „wer schneller @WebMvcTest versehentlich in @SpringBootTest verwandelt“.

Ein typisches Testgerüst kann so aussehen:

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

@WebMvcTest(EditorArticleController.class)
// In diesem Test interessiert uns der Upload-Vertrag, nicht die Sicherheit – Filter schalten wir aus, damit sie nicht stören
@AutoConfigureMockMvc(addFilters = false)
class EditorArticleControllerUploadWebMvcTest {

    // Den Service mocken wir: Der Slice-Test soll die Web-Grenze prüfen, nicht die Interna der Businesslogik
    @MockitoBean
    ArticleAttachmentService attachmentService;
}

Hier gibt es eine ehrliche Nuance: Der Endpoint liegt in der Editor-Zone und ist in der echten Anwendung fast sicher geschützt. Aber solange wir nur den HTTP-Vertrag des Uploads und die Fehlerszenarien des Multipart prüfen, können die Sicherheitsfilter nur stören. Wir schalten sie in dieser Klasse aus, damit der Test eine einzige Frage beantwortet: Wird der Multipart an der Web-Grenze korrekt verarbeitet? Ohne Vermischung mit Zugriffsprüfungen.

5. Happy Path: Prüfung des Antwortvertrags

Im Happy Path gibt es eine tückische Sache: Der Student testet oft „Status 200“ und hält das für ausreichend. Bei einem Upload-Endpoint ist das besonders gefährlich, weil der Client in der Regel dringend die Metadaten und die ID zurückbekommen muss. Wenn wir den Payload nicht prüfen, schützt der Test den Vertrag nicht: Jemand ändert das Feld originalFilename in name – und die UI zeigt plötzlich keinen Dateinamen mehr an, während der Test grün und zufrieden bleibt (im Gegensatz zum Benutzer).

Zuerst erstellen wir einen MockMultipartFile. Er benötigt den Namen des Multipart-Feldes (muss mit @RequestPart("file") übereinstimmen), den Dateinamen, den Content-Type und die Bytes.

import org.springframework.mock.web.MockMultipartFile;

// "file" ist der Name des Multipart-Feldes, muss mit @RequestPart("file") im Controller übereinstimmen
MockMultipartFile file = new MockMultipartFile(
        "file",
        "cover.png",     // Dateiname, den der Server als originalFilename sieht
        "image/png",     // MIME-Typ, den der Client im Multipart angibt
        new byte[] {1, 2, 3} // Dateiinhalt (im Test reicht ein kleines Array)
);

Jetzt konfigurieren wir den Mock des Services und senden die Anfrage. Ich halte das Beispiel bewusst kurz: Wir prüfen einige wichtige Felder, nicht das gesamte JSON „bis zum letzten Komma“.

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.springframework.http.MediaType;

// Wir legen fest, dass der Service Metadaten des Anhangs zurückgibt – das ist der Vertrag einer erfolgreichen Antwort
when(attachmentService.upload(10L, file))
        .thenReturn(new ArticleAttachmentResponse(3L, "cover.png", "image/png", 3));

// multipart(...) baut eine multipart/form-data-Anfrage; .file(file) fügt einen Part mit dem Namen "file" hinzu
mockMvc.perform(
        multipart("/api/editor/articles/{id}/attachments", 10L)
                .file(file)
                .accept(MediaType.APPLICATION_JSON) // Client erwartet JSON
)
    // Prüfen Status und Schlüsselelemente des vertraglichen JSON
    .andExpect(status().isOk())
    .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.id").value(3))
    .andExpect(jsonPath("$.originalFilename").value("cover.png"));

Wenn man eine minimale „Grenzprüfung“ durch Interaktion hinzufügen möchte, kann man eine sehr zurückhaltende Prüfung durchführen, dass der Service überhaupt aufgerufen wurde. Wichtig ist, nicht in das Szenario „ich prüfe jedes Niesen und die Reihenfolge der Atemzüge des Controllers“ zu verfallen. Es ist immer noch ein Controller-Test, kein Unit-Test der Orchestrierung.

import static org.mockito.Mockito.verify;

// Minimale Prüfung: Der Controller delegiert tatsächlich an den Service
verify(attachmentService).upload(10L, file);

In einem echten Projekt verwendet man oft statt eines direkten Vergleichs von file die Konstrukte any(MultipartFile.class) und ArgumentCaptor, weil manchmal im Controller eine Wrapper- oder Anpassung erfolgt. Für ein Lehrbeispiel ist es in Ordnung, die direkte Übergabe zu prüfen, wenn der Controller wirklich nichts mit der Datei macht.

6. Fehlerszenarien des Uploads

Bei Multipart-Fehlern gibt es eine methodische Falle: Man möchte alles Mögliche abdecken (Typ, Größe, Limits, Anzahl der Anhänge, Rechte, Status des Artikels, Mondphase). Auf der Controller-Ebene führt das entweder zu Chaos oder zu Tests, die die Servicelogik prüfen. Wir brauchen einen vernünftigen Satz an Fehlern, die wirklich zur HTTP-Grenze gehören.

Um nicht durcheinanderzukommen, ist es hilfreich, im Voraus zu verstehen, wo genau der Fehler auftritt. Manche Fehler passieren bereits „vor Ihrem Code“ (Spring konnte den Part nicht finden), andere im Controller (Prüfung von isEmpty()), wieder andere im Service (Business-Validierung, Limit bezüglich Größe/Typ/Anzahl). Ein MVC-Test kann alle drei prüfen, aber der Sinn der Prüfungen ist unterschiedlich.

Hier ist eine kompakte Tabelle, die hilft, den Kopf zu ordnen:

Szenario Wo bricht es Was wir mit dem Test beweisen wollen Typisches HTTP-Ergebnis
Multipart-Feld file fehlt Binding vor dem Controller Stabiles ApiProblem, nicht „irgendwas ist abgestürzt“ 400
Datei ist leer (0 bytes) Controller/Validierung Client erhält verständliche Ablehnung 400
Nicht unterstützter Content-Type Service/Validierung Fehler in ApiProblem mit Code übersetzt 400
Datei zu groß Service/Validierung Wir stellen errorCode=ATTACHMENT_TOO_LARGE fest 400
Artikel nicht gefunden Service Korrekte Fehlerklasse, nicht 400 404

Im Folgenden besprechen wir einige Schlüsselfälle.

Leere Datei

Eine leere Datei ist ein überraschend häufiges reales Problem. In dieser Lektion betrachten wir es als Fehler genau der Web-Grenze: Der Part ist vorhanden, aber es gibt keine Daten, und die Anfrage wird noch vor der Businesslogik abgewiesen. Daher ist dieses Szenario nicht dasselbe wie „Dateityp nicht unterstützt“ oder „Datei zu groß“: Dort wurde der Multipart bereits akzeptiert, hier besteht die Eingabe selbst die grundlegende Prüfung nicht.

Beispieltest (wir senden new byte[0], erwarten 400 und ein stabiles Fehlerformat). Hier führe ich eine Prüfung des errorCode durch, weil das der „vertraglichste“ Teil des Fehlers ist. Wenn in Ihrem ApiProblem bei solchen Fehlern violations verwendet wird, können Sie auch das prüfen, aber machen Sie den Test nicht zu einer Enzyklopädie aller Felder.

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

// Leere Datei: Part ist vorhanden, aber keine Daten
MockMultipartFile empty = new MockMultipartFile(
        "file", "empty.png", "image/png", new byte[0]
);

mockMvc.perform(
        multipart("/api/editor/articles/{id}/attachments", 10L)
                .file(empty)
)
    // Erwarte Ablehnung gemäß Vertrag (400 + errorCode)
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.status").value(400))
    .andExpect(jsonPath("$.errorCode").value("ATTACHMENT_EMPTY"));

Ein wichtiger Punkt zur Grenze: Wenn eine leere Datei auf der Web-Ebene abgefangen wird, darf der Service nicht aufgerufen werden. Sonst ließe man „Müll“ ins System.

import static org.mockito.Mockito.verifyNoInteractions;

// Grenze: Bei ungültigem Multipart auf der Web-Schicht sollten wir die Anfrage nicht „durchlassen“ zum Service
verifyNoInteractions(attachmentService);

Ja, das ist ein Interaction-Assertion, und wir verwenden es vorsichtig: nicht um „den Aufrufszenarien zu beschreiben“, sondern um die Grenze zu markieren. Wie ein Schild „Zutritt zum Service nur mit Ausweis“ – ohne Ausweis nicht eintreten.

Falscher Name des Multipart-Feldes

Einer der ärgerlichsten Fehler in Upload-APIs ist, wenn der Frontend-Entwickler die Datei nicht im Feld "file" sendet, sondern z.B. in "attachment" oder "document". Der Server sagt dann ganz ehrlich: „Ich sehe den benötigten Teil der Anfrage nicht.“ Das ist kein Bug des Frontends und kein Bug des Backends, es ist eine Vertragsverletzung. Und genau deshalb lohnt es sich, das mit einem Test zu fixieren.

Aus Sicht von Spring wird daraus normalerweise eine MissingServletRequestPartException. Unser Ziel: sicherzustellen, dass diese in ein normales ApiProblem übersetzt wird und nicht als „500, weil jemand Multipart vergessen hat“ zum Client geht.

// Name des Multipart-Feldes ist falsch: Controller erwartet "file", wir senden "document"
MockMultipartFile wrongField = new MockMultipartFile(
        "document", "cover.png", "image/png", new byte[] {1, 2, 3}
);

mockMvc.perform(
        multipart("/api/editor/articles/{id}/attachments", 10L)
                .file(wrongField)
)
    // Fehler sollte verständlich und vertraglich sein (400 + korrekte Form von ApiProblem)
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.status").value(400))
    .andExpect(jsonPath("$.title").exists());

Hier prüfen wir nicht den genauen detail, weil die Texte der Exceptions manchmal bei Spring-Upgrades wechseln. Aber der Status und die Form des Payloads – das ist unser Vertrag.

Und nochmal: Der Service sollte nicht aufgerufen werden, weil die Anfrage ihn gar nicht erreicht hat.

import static org.mockito.Mockito.verifyNoInteractions;

// Der Service wird nicht erreicht: Spring konnte den erforderlichen Part "file" nicht matchen
verifyNoInteractions(attachmentService);

Nicht unterstützter Content-Type

Jetzt ein Szenario, in dem der Multipart korrekt ist, die Datei nicht leer, aber der Dateityp nicht passt. Zum Beispiel erlauben Sie image/png, image/jpeg, application/pdf, und der Client sendet application/x-msdownload (der Moment, in dem das Backend vermutet, dass ihm ein „nützliches Dienstprogramm“ untergeschoben werden soll, das sich seltsamerweise cover.png.exe nennt).

Im MVC-Slice wollen wir nicht die gesamte Logik der Typfilterung prüfen – das ist der Service-/Regel-Layer. Aber wir wollen beweisen, dass der Client, wenn der Service einen Typ für unzulässig hält, eine verständliche Fehlermeldung mit Code sieht.

import static org.mockito.Mockito.when;

// Multipart ist korrekt, aber der Content-Type ist „verdächtig“ und laut Geschäftsregeln verboten
MockMultipartFile exe = new MockMultipartFile(
        "file", "cover.exe", "application/x-msdownload", new byte[] {7, 7, 7}
);

// Simuliere den Business-Fehler des Services: Genau dieser Code sollte in ApiProblem erscheinen
when(attachmentService.upload(10L, exe))
        .thenThrow(new ContentHubException("ATTACHMENT_TYPE_NOT_ALLOWED"));

mockMvc.perform(
        multipart("/api/editor/articles/{id}/attachments", 10L)
                .file(exe)
)
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.errorCode").value("ATTACHMENT_TYPE_NOT_ALLOWED"));

Beachten Sie die methodische Bedeutung: Wir „raten“ nicht, welche Typen erlaubt sind. Wir stellen fest, dass bei einem bestimmten Business-Fehler der externe Vertrag stabil ist.

Datei zu groß

Größenbeschränkungen sind das zweite ewige Thema nach „das falsche Feld“. Dabei gibt es zwei Arten von Beschränkungen: infrastrukturelle (Multipart-Limit im Server) und anwendungsspezifische (Ihre Regel „nicht größer als N MB“). Für den Controller-Layer ist der anwendungsspezifische Layer interessanter: Er sollte dem Client einen verständlichen Fehler ausgeben, auch wenn die Infrastruktur die Datei prinzipiell annehmen könnte.

Der Test kann so geschrieben werden:

// „Große Datei“: Hier erzeugen wir einfach ein auffällig großes Array, müssen aber nicht mit echten Megabytes aufwarten
MockMultipartFile big = new MockMultipartFile(
        "file", "big.png", "image/png", new byte[5_000_000]
);

// Simuliere die Service-Regel: Größe überschritten, daher wird eine Domain-Exception geworfen
when(attachmentService.upload(10L, big))
        .thenThrow(new ContentHubException("ATTACHMENT_TOO_LARGE"));

mockMvc.perform(
        multipart("/api/editor/articles/{id}/attachments", 10L)
                .file(big)
)
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.status").value(400))
    .andExpect(jsonPath("$.errorCode").value("ATTACHMENT_TOO_LARGE"));

Hier ein wichtiger praktischer Hinweis: In einer echten Testsuite möchte man oft nicht wirklich riesige Byte-Arrays erzeugen (Speicher, Geschwindigkeit, und so weiter). Man kann die Bytes klein halten und den Mock-Service einfach die gewünschte Exception werfen lassen. Der Sinn des Tests ist nicht, den Server „wirklich zu überlasten“, sondern den Fehlervertrag stabil zu halten.

Artikel nicht gefunden

Dieses Szenario ist nicht rein file-spezifisch, taucht aber oft im Zusammenhang mit Uploads auf. Der Client versucht, einen Anhang zu einem Artikel hochzuladen, der nicht existiert. Das ist kein Formatfehler der Anfrage: Die Anfrage ist formal korrekt. Es ist ein Fehler wegen fehlender Ressource, also 404.

// Gültige Datei, aber ungültige Ressource: Artikel mit dieser ID existiert nicht
MockMultipartFile file = new MockMultipartFile(
        "file", "cover.png", "image/png", new byte[] {1, 2, 3}
);

// Simuliere den Domain-Fehler „Ressource nicht gefunden“ auf Service-Ebene
when(attachmentService.upload(999L, file))
        .thenThrow(new ContentHubException("ARTICLE_NOT_FOUND"));

mockMvc.perform(
        multipart("/api/editor/articles/{id}/attachments", 999L)
                .file(file)
)
    .andExpect(status().isNotFound())
    .andExpect(jsonPath("$.status").value(404))
    .andExpect(jsonPath("$.errorCode").value("ARTICLE_NOT_FOUND"));

Das ist ein guter „Sicherungstest“ gegen versehentliche Verschlechterung: Manchmal fängt ein Entwickler an, fälschlicherweise 400 für alles Mögliche zurückzugeben, und die API wird schwer benutzbar. Ein solcher Test hilft, nicht auszuufern.

7. Wie man Multipart-Tests nicht zerbrechlich macht

Einen Multipart-Endpoint kann man leicht in einen Testsumpf verwandeln: „Ach, prüfen wir doch alles bis zum 127. Byte“. Aber ein MVC-Slice-Test sollte kein Archiv aller internen Entscheidungen des Services und des Storages werden. Sonst erhält man teure, lärmende und ständig kaputtgehende Tests, die nicht helfen, sondern Freitagabend Opfergaben verlangen.

Ein gesundes Minimum für Upload-Tests ist normalerweise: Im Happy Path prüft man Status, Media-Typ der Antwort und ein paar Schlüsselfelder des JSON (id, originalFilename, size). Bei negativen Szenarien prüft man Status und errorCode (oder violations, wenn es genau ein Validierungsfehler ist), wobei man die Prüfung von „hübschen Meldungen“ und genauen Exception-Texten vermeidet. Wenn die Anfrage auf der Web-Grenze ungültig ist (kein Part, leere Datei), kann man zusätzlich prüfen, dass der Service nicht aufgerufen wurde – das ist ein gutes Merkmal einer korrekten Grenze.

Wenn Sie das Gefühl haben, dass in jedem Test die Erstellung von MockMultipartFile kopiert wird, können Sie einen winzigen Helper einführen. Hauptsache, er verbirgt nicht den Sinn des Tests (sobald der Helper anfängt, die gesamte Anfrage „magisch“ zusammenzubauen, verliert der Student den Überblick).

private static MockMultipartFile png(String filename, byte[] bytes) {
    // Der Helper stellt fest: Part heißt immer "file", Typ ist image/png
    return new MockMultipartFile("file", filename, "image/png", bytes);
}

Und der Test liest sich dann menschlich: png("cover.png", new byte[] {1, 2, 3}) – und man erkennt sofort, dass es genau der file-part "file" ist.

8. Typische Fehler beim Testen von Multipart-Uploads

Fehler Nr. 1: Falscher Name des Multipart-Feldes und „warum 400“
Am häufigsten fallen Upload-Tests nicht wegen der Businesslogik, sondern weil in MockMultipartFile das Feld "attachment" statt "file" angegeben wurde. In der echten Anwendung zerbricht das genau so den Vertrag: Der Client hat „den falschen Teil des Briefes“ geschickt. Der Test sollte Ihnen helfen, solche Dinge schnell zu erkennen, und sie nicht zu einer zweistündigen Debug-Session „Spring hat schon wieder was nicht verstanden“ machen.

Fehler Nr. 2: Versuch, das echte Dateisystem in @WebMvcTest zu prüfen
Manchmal will man new File("src/test/resources/...") verwenden und „richtig“ eine Datei hochladen. Das macht den Test abhängig von Pfaden, Umgebung und zufälligen Eigenheiten. @WebMvcTest handelt von der HTTP-Grenze, daher ist MockMultipartFile fast immer besser: schnell, deterministisch und nicht an die Platte gebunden.

Fehler Nr. 3: Vermischung verschiedener Fehlerursachen in einem Test
Leere Datei, nicht unterstützter Typ und zu große Größe sind drei verschiedene Szenarien. Wenn Sie in einem Test „leer und gleichzeitig nicht unterstützt und auch noch groß“ machen, erhalten Sie einen Test, der nichts klar beweist. Er wird je nach zufälliger Priorität der Prüfungen rot/grün werden und keine Dokumentation des Verhaltens sein.

Fehler Nr. 4: Prüfung interner Interaktionen statt des HTTP-Vertrags
Manchmal ist verify(service).upload(...) nützlich, aber sobald der Test zu „der Controller muss Methode A aufrufen, dann B, dann C, und zwar genau einmal und in dieser Reihenfolge“ wird, testen Sie die Implementierung, nicht den Vertrag. Ein solcher Test bricht bei harmlosem Refactoring und gibt dem API-Client keine Sicherheit.

Fehler Nr. 5: Zu große Byte-Arrays in Tests und „warum der Suite langsam wurde“
Ein new byte[50_000_000] zu erzeugen, um zu „beweisen, dass die Datei groß ist“, ist fast immer überflüssig. Wenn Sie das Verhalten bei ATTACHMENT_TOO_LARGE prüfen, reicht meist eine kleine Datei und eine Exception vom Mock-Service. Große Arrays verwandeln einen schnellen MVC-Suite in einen Mini-Speicher-Benchmark, und unser Kurs handelt nun einmal vom Testen, nicht vom Stresstest des Laptops.

1
Aufgabe
Spring Test, Level 13, Lektion 1
Gesperrt
Hochladen eines Anhangs
Hochladen eines Anhangs
1
Aufgabe
Spring Test, Level 13, Lektion 1
Gesperrt
Leere Datei als Fehler in der Anfrage
Leere Datei als Fehler in der Anfrage
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION