2014-09-12 11 views
7

Ich habe einen Swift-App, die NSFetchedResultsControllerList Objekte aus persistenten Speichern zu holen verwendet:Wie Unit-Test NSFetchedResultsController in Swift

let fetchedResultsController: NSFetchedResultsController = ... 
var error : NSError? 
fetchedResultsController.performFetch(&error) 
if let error = error { 
    NSLog("Error: \(error)") 
} 
let lists: [List] = fetchedResultsController.fetchedObjects! as [List] 
NSLog("lists count = \(lists.count)") 
for list: List in lists { 
    NSLog("List: \(list.description)") 
} 

und es wie erwartet funktioniert, ich bin immer List Beschreibungen Objekte in dem ausgedruckten Konsole. Ich möchte einige Komponententests für meine App schreiben, also habe ich Klasse erstellt, die XCTestCase erweitert. Der Code kompiliert ohne Probleme, Tests laufen, aber leider kann ich die List Objekte in diesem Zusammenhang nicht abrufen.

Alles, was ich bin in der Konsole immer Anzahl der List Objekte und ein fataler Fehler:

lists count = 59 
fatal error: NSArray element failed to match the Swift Array Element type 

durch die Linie rised:

for list: List in lists { 

Ich bin mir ziemlich sicher, dass ich Ziele richtig konfiguriert ist, wie ich List Objekt erstellen und es in den Kontext von verwalteten Objekten problemlos aus dem Quellcode meiner App sowie aus Unit-Testquellcode einfügen kann. Das einzige Problem, das ich erlebe, ist das Abholen von der Testeinheit. Ich frage mich, warum das Abrufen funktioniert, wenn die App im Simulator ausgeführt wird, und fehlschlägt, wenn es während des Komponententests ausgeführt wird.

Alle Ideen, die falsch sein könnten, werden geschätzt.

Update:

Um genauer zu sein, wie meine Implementierung aussieht, ist hier komplette Codebeispiel, mit denen ich spielen:

var error: NSError? = nil 

let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) 
let applicationDocumentsDirectory = urls[urls.count-1] as NSURL 

let modelURL = NSBundle.mainBundle().URLForResource("CheckLists", withExtension: "momd")! 
let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL) 

var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) 
let url = applicationDocumentsDirectory.URLByAppendingPathComponent("CheckLists.sqlite") 
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil { 
    NSLog("Error1: \(error)") 
    abort() 
} 

var managedObjectContext = NSManagedObjectContext() 
managedObjectContext.persistentStoreCoordinator = coordinator 

let fetchRequest = NSFetchRequest() 
fetchRequest.entity = NSEntityDescription.entityForName("List", inManagedObjectContext: managedObjectContext) 
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "name", ascending: true) ] 

let fetchedResultsController = NSFetchedResultsController(
    fetchRequest: fetchRequest, 
    managedObjectContext: managedObjectContext, 
    sectionNameKeyPath: nil, 
    cacheName: "ListFetchedResultsControllerCache" 
) 

fetchedResultsController.performFetch(&error) 
if let error = error { 
    NSLog("Error2: \(error)") 
    abort() 
} 

let fetchedObjects: [AnyObject]? = fetchedResultsController.fetchedObjects 
if let fetchedObjects = fetchedObjects { 
    NSLog("Fetched objects count: \(fetchedObjects.count)") 
    for fetchedObject in fetchedObjects { 
     NSLog("Fetched object: \(fetchedObject.description)") 
    } 
} 
else { 
    NSLog("Fetched objects array is nil") 
} 

let fetchedLists: [List]? = fetchedResultsController.fetchedObjects as? [List] 
if let fetchedLists = fetchedLists { 
    NSLog("Fetched lists count: \(fetchedLists.count)") 
    for fetchedList in fetchedLists { 
     NSLog("Fetched list: \(fetchedList.description)") 
    } 
} 
else { 
    NSLog("Fetched lists array is nil") 
} 

Wenn ich es von meiner App-Quellcode ausführen, Wenn Sie die App im Simulator ausführen, sieht die Konsolenausgabe wie folgt aus:

Fetched objects count: 3 
Fetched object: <CheckLists.List: 0x7a6866f0> (entity: List; id: 0x7a686020 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p2> ; data: { 
    name = "List 1"; 
}) 
Fetched object: <CheckLists.List: 0x7a686930> (entity: List; id: 0x7a686030 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p1> ; data: { 
    name = "List 2"; 
}) 
Fetched object: <CheckLists.List: 0x7a686970> (entity: List; id: 0x7a686040 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p3> ; data: { 
    name = "List 3"; 
}) 
Fetched lists count: 3 
Fetched list: <CheckLists.List: 0x7a6866f0> (entity: List; id: 0x7a686020 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p2> ; data: { 
    name = "List 1"; 
}) 
Fetched list: <CheckLists.List: 0x7a686930> (entity: List; id: 0x7a686030 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p1> ; data: { 
    name = "List 2"; 
}) 
Fetched list: <CheckLists.List: 0x7a686970> (entity: List; id: 0x7a686040 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p3> ; data: { 
    name = "List 3"; 
}) 

Allerdings, wenn ich diesen Code von einem ausführen Unit-Test, bekomme ich diese Ausgabe:

Fetched objects count: 3 
Fetched object: <CheckLists.List: 0x7a07df50> (entity: List; id: 0x7a07d7e0 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p2> ; data: { 
    name = "List 1"; 
}) 
Fetched object: <CheckLists.List: 0x7a07e190> (entity: List; id: 0x7a07d7f0 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p1> ; data: { 
    name = "List 2"; 
}) 
Fetched object: <CheckLists.List: 0x7a07e1d0> (entity: List; id: 0x7a07d800 <x-coredata://7A87B5BE-C2FA-4150-B9E3-879FDE07F0B9/List/p3> ; data: { 
    name = "List 3"; 
}) 
Fetched lists array is nil 

Ich hoffe, es macht es leichter zu verstehen, wo das Problem liegt. Irgendwie diese Aussage:

let fetchedLists: [List]? = fetchedResultsController.fetchedObjects as? [List] 

produziert eine Reihe von List Objekten, wenn app im Simulator ausgeführt wird, aber es funktioniert nicht nil erzeugen, wenn von Unit-Test durchgeführt.

+0

Haben Sie Ihre Objektmodelldatei ('momd') in Ihrem Testpaket enthalten? Und ist die Modelldatei so eingerichtet, dass sie die Unterklasse 'List' für die Listenentität verwendet? – MrAlek

+0

@MrAlek - Ich bin mir nicht sicher, ob meine '.momd' Datei im Testpaket enthalten ist. Ich erstelle ein solches Modell: 'NSManagedObjectModel (NSBundle.mainBundle(). URLForResource (" CheckLists ", mitExtension:" momd ")!)' Und es funktioniert sowohl für das Hauptziel als auch für das Testziel. Könnten Sie genauer sein? – Darrarski

+0

@MrAlek - In meiner '.xcdatamodeld' Datei habe ich die korrekte Klasse für' List' Entität eingerichtet. Ich kann sogar neue Entitäten in meinen Komponententests erstellen und sie in den Kontext einfügen. Es bleibt in der Datenbank bestehen, wenn der Kontext gespeichert wird. – Darrarski

Antwort

2

Das Problem ist mit der Zielkonfiguration verbunden. Ich habe das Problem mit einer kleinen Problemumgehung gelöst.

Zuvor, um List Entitätsklasse in meinem Unit-Test-Ziel zugänglich zu machen, habe ich es zu diesem Ziel hinzugefügt. Also, die List Klasse war in zwei Ziele. Tatsächlich gab es zwei von Swift bekannte Klassen, eine für jedes Ziel: MyAppTarget.List und MyUnitTestTarget.List. Der Controller für zurückgeworfene Ergebnisse gibt das Array MyAppTarget.List Objekte zurück, aber im Einheitentestziel wurde List als MyUnitTestTarget.List Klasse angenommen.Das ist viel Diese Codezeile:

let fetchedLists: [List]? = fetchedResultsController.fetchedObjects as? [List] 

produzierte nil wenn von der Einheit Testziel ausgeführt wird, und nicht die richtige Anordnung als wenn sie von Hauptziel durchgeführt. Um dies zu beheben habe ich es nur zu:

let fetchedLists: [MyAppTarget.List]? = fetchedResultsController.fetchedObjects as? [MyAppTarget.List] 

und machen die List Klasse Öffentlichkeit. Nach dieser Änderung funktioniert es wie erwartet.

Aber noch ist es ein bisschen für mich verwirrend, dass MyAppTarget.List nicht zu MyUnitTestTarget.List gegossen werden. Darüber hinaus bedeutet das, dass ich jede Entität Unterklasse veröffentlichen muss, um sie innerhalb von Komponententests zu verwenden. Bisher habe ich keine bessere Lösung gefunden.

Vielleicht gibt es einen besseren Weg, um dieses Problem zu lösen. Ich sehe keine Option NSFetchedResultsController zu sagen, dass es MyAppTarget.List im Hauptziel und MyUnitTestTarget.List im Einheitentestziel zurückgeben sollte. Es wird immer die Konfiguration aus der Datei .xcdatamodeld für eine gegebene Entität verwenden. Auch wenn es eine Möglichkeit gibt, MyAppTarget.List in MyUnitTestTarget.List innerhalb eines Komponententests zu übertragen, muss die Klasse List weiterhin öffentlich sein.

Update:

ich einen Weg für andere Klasse von Entitäten, die von NSFetchedResultsController im laufenden Betrieb zurück gefunden haben. Es ist eine klarere und einfachere Lösung: https://stackoverflow.com/a/25858758/514181

Es ermöglicht die nahtlose Verwendung von CoreData-Entities in Komponententests, ohne die Entity-Klasse zu publizieren.

+0

Hast du irgendwo ein Beispiel? Ich versuchte diesen Ansatz, aber bekam Fehler zurück, die sagten, dass die Klasse nicht gefunden wurde, also benutzte sie NSManagedObject –

+1

Ich habe Beispielquellcode hier angeschlossen: http://stackoverflow.com/a/25858758/514181 – Darrarski

+1

Ich versuchte dieses aber nein Freude. Ich habe ein einfaches Beispiel unter https://github.com/ztolley/Frazzle –