Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

You Don't Need MVVM to Test SwiftUI

protocol MovieLoader { func load() async throws -> [Movie] } struct MovieList: View { @State var movies = [Movie]() let loader: MovieLoader var body: some View { List(movies, rowContent: MovieRow.init) …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

protocol MovieLoader {
func load() async throws -> [Movie]
}

struct MovieList: View {
@State var movies = [Movie]()
let loader: MovieLoader
var body: some View {
List(movies, rowContent: MovieRow.init)
.task { await load() }
}

func load() async {
movies = (try? await loader.load()) ?? []
}
}

func test_load_loadsMovies() async {
let expected = [anyMovie()]
let stubbed = StubMovieLoader(stubs: expected)
let sut = MovieList(loader: stubbed)
await sut.load()
XCTAssertEqual(sut.movies, expected)
}






If you try to run this test as-is, it will fail.



Thehe reason is that @State in a SwiftUI view only connects to an in-memory store if the view has been mounted into a real view hierarchy. The most common workaround is to move the logic into an @Observable.




@Observable class MovieListViewModel {
var movies = [Movie]()
let loader: MovieLoader
init(loader: MovieLoader = RemoteMovieLoader()) { ... }
func load() async {
movies = (try? await loader.load()) ?? []
}
}

struct MovieList: View {
@State var vm = MovieListViewModel()
var body: some View {
List(vm.movies, content: MovieRow.init)
.task { await vm.load() }
}
}

func test_load_loadsMovies() async {
let expected = [anyMovie()]
let stubbed = StubMovieLoader(stubs: expected)
let sut = MovieListViewModel(loader: stubbed)
await sut.load()
XCTAssertEqual(sut.movies, expected)
}






This pattern, widely used and considered a de facto standard, comes with some drawbacks:




  1. We abandon value types purely for the sake of testability.

  2. We introduce reference types with all their baggage (lifecycle management and potential leaks) into a framework designed to be as value type-oriented as possible.

  3. We keep mixing UI state logic (how and when it updates) with the storage itself, despite introducing an extra object to abstract it — even though stateless MVVM implementations exist even in UIKit.



The solution, curiously, has been in SwiftUI from the start.

In this article I'd like to present an alternative, building on the work of Lazar Otasevic.





Binding



@State is not testable outside a view hierarchy, but @Binding offers a way to expose state for testing — something that seems to have gone largely unnoticed by the community.

Conceptually, a @Binding can be understood as a simple pair of closures (get and set):




struct Binding<Value> {
let get: () -> Value
let set: (Value) -> Void
}






Which makes a view using @Binding fully testable:




struct MovieList: View {
@Binding var movies: [Movie]
let loader: MovieLoader
var body: some View {
List(movies, rowContent: MovieRow.init)
.task { await load() }
}

func load() async {
movies = (try? await loader.load()) ?? []
}
}

func test_load_loadsMovies() async {
var storage = [Movie]()
let binding = Binding(get: { storage }, set: { storage = $0 })
let expected = [anyMovie()]
let stubLoader = StubMovieLoader(stubs: expected)
let sut = MovieList(movies: binding, loader: stubLoader)
await sut.load()
XCTAssertEqual(storage, expected)
}






The view no longer owns its state, becoming a purely functional component that delegates persistence to its ancestor.




SwiftUI is already a state engine; @Binding is simply the wire that lets us connect that engine to our unit tests.




We can build some helper utilities. Analogous to Apple's static Binding.constant(), we can have a Binding.variable():




extension Binding {
static func variable(_ initialValue: Value) -> Self {
var copy = initialValue
return Binding(get: { copy }, set: { copy = $0 })
}
}

func test_load_loadsMovies() async {
let expected = [anyMovie()]
let stubLoader = StubMovieLoader(stubs: expected)
let (sut, movies) = makeSUT(loader: stubLoader)
await sut.load()
XCTAssertEqual(movies(), expected)
}

func makeSUT(movies: [Movie] = [], loader: MovieLoader) -> (MovieList, () -> [Movie]) {
let movies = Binding.variable(movies)
let sut = MovieList(movies: movies, loader: loader)
return (sut, { movies.wrappedValue })
}






Binding.variable doesn't create a special container like @State ; it simply captures a local variable via closures. When the view writes:




movies = newValue






...it's actually executing the set closure, which mutates copy.



And when you read in the test:




movies.wrappedValue






...you're executing the get closure, which returns that same copy. The view and the test share the same storage, since both use the captured variable.



The view's state logic is fully testable. Whoever builds it needs to provide it with its state:




import Movies   
import MoviesUI

// Composition Root
struct MovieListComposer: View {
@State var movies = [Movie]()
let loader: MovieLoader
var body: some View {
MovieList(movies: $movies, loader: loader)
}
}






We can also create a generic wrapper for views using this pattern:




struct Host<Initial, Content: View>: View {
typealias Binding = SwiftUI.Binding<Initial>

@State var state: Initial
let content: (Binding) -> Content

init(
_ state: Initial,
@ViewBuilder content: @escaping (Binding) -> Content
) {
self.state = state
self.content = content
}
var body: some View {
content($state)
}
}

struct SomeApp: App {
var body: some Scene {
WindowGroup {
Host([Movie]()) {
MovieList(movies: $0)
}
}
}
}









Conclusions and Considerations



This pattern enables a high level of testability while preserving the simplicity of SwiftUI's declarative system, without intermediate layers.




  • You don't need a @observable ViewModel to test state logic in SwiftUI if you don't need idenitity for your specific use case (most cases).


  • @State has storage that is inaccessible outside the SwiftUI runtime. @Binding has no storage of its own and only represents access to external storage via closures.

  • By using @Binding, we keep the view as a lightweight, testable struct, without forcing the creation of a class just to satisfy the test runner.

  • You can still use @Observable for state, since @Binding is the communication interface, not the storage. This decouples the view from how the data is stored (whether in @State , an @Observable, or a property wrapper from CoreData/SwiftData/etc.).

  • Encapsulating logic in a dedicated struct for reuse across views and a more decoupled architecture is entirely possible. For more details, I recommend the the "True Logic: Stateless and Pure" section of this article from Lazar Otasevic. I've also published an example project that can serve as a reference: OnlyGoodMovies on how to architecture a project around this design pattern.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Vulnerability Remediation & Verification
1 Warnungen
title: Detect Exploitation - You Don't Need MVVM to Test SwiftUI
id: b3367dfb-5101-469c-85c7-cc73e05c87f2
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-26
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-26"
        description = "YARA Signature for "
    strings:
        $str = "You Don\'t Need MVVM to Test Sw" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("You Dont Need MVVM to Test SwiftUI")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*You Dont Need MVVM to Test SwiftUI*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "You Dont Need MVVM to Test SwiftUI"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich You Don&#039;t Need MVVM to Test SwiftUI.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten You Don't Need MVVM to Test SwiftUI

Thematisch verwandte Begriffe: Dont, Need, MVVM, Test · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-88003 | InvoicePlane is a self-hosted open source application for managing invoi…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag