Closed jwb closed 3 years ago
Hey, @jwb. Although this might not be the problem, make sure to include DeepFileManager
's dependencies in the Cuckoo generator call as well. They are needed for correct inheritance detection.
As for this situation, I can't infer the cause based on the limited information provided. Can you share DeepFileManager
structure as well as the full command you're using to generate mocks? If not, would you be able to replicate this problem in a minimal working example?
Here's the DeepFileManager:
import UIKit
class DeepFileManager {
static let shared = DeepFileManager()
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func getFileUrl(audioId: String) -> URL {
let fileName = audioId + ".wav"
let fileURL = getDocumentsDirectory().appendingPathComponent(fileName)
return fileURL
}
func fileExists(audioId: String) -> Bool {
let fileURL = getFileUrl(audioId: audioId)
return FileManager.default.fileExists(atPath: fileURL.path)
}
func findOldRecordings() -> [URL] {
do {
let directoryContents = try FileManager.default.contentsOfDirectory(at: getDocumentsDirectory(),
includingPropertiesForKeys: nil,
options: [])
var filesPath = directoryContents.filter{ $0.pathExtension == "wav" }
let calendar = Calendar(identifier: .gregorian)
var dateComponents = DateComponents()
dateComponents.year = 2020
dateComponents.month = 12
dateComponents.day = 13
let lastRecordingDate = calendar.date(from: dateComponents)!
filesPath = filesPath.filter {
do {
let fileAttrs = try FileManager.default.attributesOfItem(atPath: "\($0.path)")
if let fileDate = fileAttrs[.creationDate] as? Date {
if fileDate < lastRecordingDate {
return true
}
}
} catch { }
return false
}
return filesPath
} catch {
print("getAllRecordingFiles error")
}
return [URL]()
}
// DeepFileManager methods for iOS only.
#if os(iOS)
func getAudioRecordingURL(audioId: String) -> URL {
let fileName = UserDefaults.standard.getAudioRecordingId(audioId: audioId) + ".wav"
let fileURL = getDocumentsDirectory().appendingPathComponent(fileName)
return fileURL
}
func getTotalStorageCapacity() -> Int64? {
let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
do {
let values = try fileURL.resourceValues(forKeys: [.volumeTotalCapacityKey])
if let capacity = values.volumeTotalCapacity {
return Int64(capacity)
} else {
print("DeepFileManager getStorageCapacity error: Capacity is unavailable")
}
} catch {
print("DeepFileManager getStorageCapacity error: \(error.localizedDescription)")
}
return nil
}
func getAvailableStorageCapacity() -> Int64? {
let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
do {
let values = try fileURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
if let capacity = values.volumeAvailableCapacityForImportantUsage {
return capacity
} else {
print("DeepFileManager getStorageCapacity error: Capacity is unavailable")
}
} catch {
print("DeepFileManager getStorageCapacity error: \(error.localizedDescription)")
}
return nil
}
func getRemainingMinutesForRecording() -> Int? {
if let storageCapacity = getAvailableStorageCapacity() {
// 6 MB per minute
let recordingPerMinute = Int64(6000000)
let remainingMinutes = storageCapacity/recordingPerMinute
return Int(remainingMinutes)
}
return nil
}
#endif
}
Here's the script in the Xcode build
# Define output file.
OUTPUT_FILE="${PROJECT_DIR}/${PROJECT_NAME}Tests/GeneratedMocks.swift"
echo "Generated Mocks File = ${OUTPUT_FILE}"
for ((i = 0; i < ${SCRIPT_INPUT_FILE_COUNT}; i ++ ))
do var=SCRIPT_INPUT_FILE_$i
list="$list ${(P)var}"
done
# Generate mock files, include as many input files as you'd like to create mocks for.
"${PODS_ROOT}/Cuckoo/run" generate --testable "${PROJECT_NAME}" \
--output "${OUTPUT_FILE}" $list
The only input for the script is DeepFileManager.swift.
Which platform are you running the tests for? I'm thinking it might be the conditional #if
. Do we have support for that in Cuckoo @MatyasKriz ?
I'd be surprised if we did, at least I don't remember adding it. Though it may not be too hard to add when we find the time if SourceKit parses this.
I removed everything from the #if
, to the #endif
(inclusive), and it still complains:
RecordingTests.swift:26:21: Global function 'stub(_:block:)' requires that 'DeepFileManager' conform to 'Mock'
Why would Swift want the superclass of the MockDeepFileManager to conform to 'Mock'?
After that excision, DeepFileManager
uses FileManager
, URL
, Calendar
, and DateComponents
. Do I need to point the generator at source for those? I dunno what the path for the Foundation files is.
Could you share the generated mock class source?
// MARK: - Mocks generated from file: Shared/DeepFileManager.swift at 2021-01-21 23:22:05 +0000
//
// DeepFileManager.swift
import Cuckoo
@testable import project
import UIKit
class MockDeepFileManager: DeepFileManager, Cuckoo.ClassMock {
typealias MocksType = DeepFileManager
typealias Stubbing = __StubbingProxy_DeepFileManager
typealias Verification = __VerificationProxy_DeepFileManager
let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: true)
private var __defaultImplStub: DeepFileManager?
func enableDefaultImplementation(_ stub: DeepFileManager) {
__defaultImplStub = stub
cuckoo_manager.enableDefaultStubImplementation()
}
override func getDocumentsDirectory() -> URL {
return cuckoo_manager.call("getDocumentsDirectory() -> URL",
parameters: (),
escapingParameters: (),
superclassCall:
super.getDocumentsDirectory()
,
defaultCall: __defaultImplStub!.getDocumentsDirectory())
}
override func getFileUrl(audioId: String) -> URL {
return cuckoo_manager.call("getFileUrl(audioId: String) -> URL",
parameters: (audioId),
escapingParameters: (audioId),
superclassCall:
super.getFileUrl(audioId: audioId)
,
defaultCall: __defaultImplStub!.getFileUrl(audioId: audioId))
}
override func fileExists(audioId: String) -> Bool {
return cuckoo_manager.call("fileExists(audioId: String) -> Bool",
parameters: (audioId),
escapingParameters: (audioId),
superclassCall:
super.fileExists(audioId: audioId)
,
defaultCall: __defaultImplStub!.fileExists(audioId: audioId))
}
override func getAllStuckRecordingFiles() -> [URL] {
return cuckoo_manager.call("getAllStuckRecordingFiles() -> [URL]",
parameters: (),
escapingParameters: (),
superclassCall:
super.getAllStuckRecordingFiles()
,
defaultCall: __defaultImplStub!.getAllStuckRecordingFiles())
}
override func getAudioRecordingURL(audioId: String) -> URL {
return cuckoo_manager.call("getAudioRecordingURL(audioId: String) -> URL",
parameters: (audioId),
escapingParameters: (audioId),
superclassCall:
super.getAudioRecordingURL(audioId: audioId)
,
defaultCall: __defaultImplStub!.getAudioRecordingURL(audioId: audioId))
}
override func getTotalStorageCapacity() -> Int64? {
return cuckoo_manager.call("getTotalStorageCapacity() -> Int64?",
parameters: (),
escapingParameters: (),
superclassCall:
super.getTotalStorageCapacity()
,
defaultCall: __defaultImplStub!.getTotalStorageCapacity())
}
override func getAvailableStorageCapacity() -> Int64? {
return cuckoo_manager.call("getAvailableStorageCapacity() -> Int64?",
parameters: (),
escapingParameters: (),
superclassCall:
super.getAvailableStorageCapacity()
,
defaultCall: __defaultImplStub!.getAvailableStorageCapacity())
}
override func getRemainingMinutesForRecording() -> Int? {
return cuckoo_manager.call("getRemainingMinutesForRecording() -> Int?",
parameters: (),
escapingParameters: (),
superclassCall:
super.getRemainingMinutesForRecording()
,
defaultCall: __defaultImplStub!.getRemainingMinutesForRecording())
}
struct __StubbingProxy_DeepFileManager: Cuckoo.StubbingProxy {
private let cuckoo_manager: Cuckoo.MockManager
init(manager: Cuckoo.MockManager) {
self.cuckoo_manager = manager
}
func getDocumentsDirectory() -> Cuckoo.ClassStubFunction<(), URL> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getDocumentsDirectory() -> URL", parameterMatchers: matchers))
}
func getFileUrl<M1: Cuckoo.Matchable>(audioId: M1) -> Cuckoo.ClassStubFunction<(String), URL> where M1.MatchedType == String {
let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: audioId) { $0 }]
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getFileUrl(audioId: String) -> URL", parameterMatchers: matchers))
}
func fileExists<M1: Cuckoo.Matchable>(audioId: M1) -> Cuckoo.ClassStubFunction<(String), Bool> where M1.MatchedType == String {
let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: audioId) { $0 }]
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "fileExists(audioId: String) -> Bool", parameterMatchers: matchers))
}
func getAllStuckRecordingFiles() -> Cuckoo.ClassStubFunction<(), [URL]> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getAllStuckRecordingFiles() -> [URL]", parameterMatchers: matchers))
}
func getAudioRecordingURL<M1: Cuckoo.Matchable>(audioId: M1) -> Cuckoo.ClassStubFunction<(String), URL> where M1.MatchedType == String {
let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: audioId) { $0 }]
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getAudioRecordingURL(audioId: String) -> URL", parameterMatchers: matchers))
}
func getTotalStorageCapacity() -> Cuckoo.ClassStubFunction<(), Int64?> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getTotalStorageCapacity() -> Int64?", parameterMatchers: matchers))
}
func getAvailableStorageCapacity() -> Cuckoo.ClassStubFunction<(), Int64?> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getAvailableStorageCapacity() -> Int64?", parameterMatchers: matchers))
}
func getRemainingMinutesForRecording() -> Cuckoo.ClassStubFunction<(), Int?> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return .init(stub: cuckoo_manager.createStub(for: MockDeepFileManager.self, method: "getRemainingMinutesForRecording() -> Int?", parameterMatchers: matchers))
}
}
struct __VerificationProxy_DeepFileManager: Cuckoo.VerificationProxy {
private let cuckoo_manager: Cuckoo.MockManager
private let callMatcher: Cuckoo.CallMatcher
private let sourceLocation: Cuckoo.SourceLocation
init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) {
self.cuckoo_manager = manager
self.callMatcher = callMatcher
self.sourceLocation = sourceLocation
}
@discardableResult
func getDocumentsDirectory() -> Cuckoo.__DoNotUse<(), URL> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return cuckoo_manager.verify("getDocumentsDirectory() -> URL", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func getFileUrl<M1: Cuckoo.Matchable>(audioId: M1) -> Cuckoo.__DoNotUse<(String), URL> where M1.MatchedType == String {
let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: audioId) { $0 }]
return cuckoo_manager.verify("getFileUrl(audioId: String) -> URL", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func fileExists<M1: Cuckoo.Matchable>(audioId: M1) -> Cuckoo.__DoNotUse<(String), Bool> where M1.MatchedType == String {
let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: audioId) { $0 }]
return cuckoo_manager.verify("fileExists(audioId: String) -> Bool", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func getAllStuckRecordingFiles() -> Cuckoo.__DoNotUse<(), [URL]> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return cuckoo_manager.verify("getAllStuckRecordingFiles() -> [URL]", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func getAudioRecordingURL<M1: Cuckoo.Matchable>(audioId: M1) -> Cuckoo.__DoNotUse<(String), URL> where M1.MatchedType == String {
let matchers: [Cuckoo.ParameterMatcher<(String)>] = [wrap(matchable: audioId) { $0 }]
return cuckoo_manager.verify("getAudioRecordingURL(audioId: String) -> URL", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func getTotalStorageCapacity() -> Cuckoo.__DoNotUse<(), Int64?> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return cuckoo_manager.verify("getTotalStorageCapacity() -> Int64?", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func getAvailableStorageCapacity() -> Cuckoo.__DoNotUse<(), Int64?> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return cuckoo_manager.verify("getAvailableStorageCapacity() -> Int64?", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
@discardableResult
func getRemainingMinutesForRecording() -> Cuckoo.__DoNotUse<(), Int?> {
let matchers: [Cuckoo.ParameterMatcher<Void>] = []
return cuckoo_manager.verify("getRemainingMinutesForRecording() -> Int?", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
}
}
}
class DeepFileManagerStub: DeepFileManager {
override func getDocumentsDirectory() -> URL {
return DefaultValueRegistry.defaultValue(for: (URL).self)
}
override func getFileUrl(audioId: String) -> URL {
return DefaultValueRegistry.defaultValue(for: (URL).self)
}
override func fileExists(audioId: String) -> Bool {
return DefaultValueRegistry.defaultValue(for: (Bool).self)
}
override func getAllStuckRecordingFiles() -> [URL] {
return DefaultValueRegistry.defaultValue(for: ([URL]).self)
}
override func getAudioRecordingURL(audioId: String) -> URL {
return DefaultValueRegistry.defaultValue(for: (URL).self)
}
override func getTotalStorageCapacity() -> Int64? {
return DefaultValueRegistry.defaultValue(for: (Int64?).self)
}
override func getAvailableStorageCapacity() -> Int64? {
return DefaultValueRegistry.defaultValue(for: (Int64?).self)
}
override func getRemainingMinutesForRecording() -> Int? {
return DefaultValueRegistry.defaultValue(for: (Int?).self)
}
}
guys any update? or @jwb have u resolved it on ur side? I just posted a problem like this one; I added the src codes as well
I've abandoned cuckoo for the time being. The Swift Mock Generator for XCode is at least a little helpful.
@jwb Sorry! I've had so much on my plate I completely forgot about your issue. I'll try to reproduce it.
@jwb Found it. when(stub.fileExists(audioId: FAKE_ID)).then()
on this line you're calling then()
which is not valid. You can either call thenReturn(x)
where x
is the value you want it to return, or use the then
, but you have to provide it with a closure representing the method implementation. So in this case, it'd be a (String) -> Bool
closure.
It's unfortunate the Swift compiler gives this unhelpful error, but I'm not sure if there's anything we can do on Cuckoo's end to improve it.
In my case I forgot the .get
part in when(mock.id.get)
😂 .
I asked a question on StackOverflow because this seems to be a Swift behavior that I don't understand.
Using Cuckoo 1.4.1, I get the error
I don't know how to proceed, because the instance passed to the
stub
function was created from the class inGeneratedMocks.swift
. The reference toDeepFileManager
is the class that is mocked.So it's a mystery to me why Swift 4 would complain about the conformance of the superclass of the instance I've passed to it. Can you lead me out of this conundrum?
Here's the code for the test:
And here's the declaration of
MockDeepFileManager
fromGeneratedMocks.swift
: