Skip to content

Commit

Permalink
Revert enforce correct usage of class and protocol mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewchang-bird committed Apr 9, 2020
1 parent 8acb442 commit 7f8638b
Show file tree
Hide file tree
Showing 9 changed files with 505 additions and 4,189 deletions.
3 changes: 0 additions & 3 deletions MockingbirdFramework/Mocking/Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ public protocol Mock {
var sourceLocation: SourceLocation? { get set }
}

/// All class initializer proxies conform to this protocol. Do not use this in your tests!
public protocol Initializable {}

/// Used to store invocations on static or class scoped methods.
public class StaticMock: Mock {
public let mockingContext = MockingContext()
Expand Down
3 changes: 1 addition & 2 deletions MockingbirdGenerator/Generator/FileGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ class FileGenerator {
let operations = mockableTypes
.sorted(by: <)
.flatMap({ mockableType -> [RenderTemplateOperation] in
let mockableTypeTemplate = MockableTypeTemplate(mockableType: mockableType,
abstractTypeNamePrefix: "")
let mockableTypeTemplate = MockableTypeTemplate(mockableType: mockableType)
let initializerTemplate = MockableTypeInitializerTemplate(
mockableTypeTemplate: mockableTypeTemplate,
containingTypeNames: []
Expand Down
128 changes: 21 additions & 107 deletions MockingbirdGenerator/Generator/Templates/MethodTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ class MethodTemplate: Template {
">": "_greaterThan",
">=": "_greaterThanOrEqualTo",
]

static let genericMockTypeName = "__ReturnType"
}

var compilationDirectiveDeclaration: (start: String, end: String) {
Expand All @@ -57,89 +55,27 @@ class MethodTemplate: Template {
return (start, end)
}

enum InitializationStyle {
case implicit, explicit, dummy, unavailable
}

struct Initializer: Hashable {
let definition: String
let body: String
let style: InitializationStyle

func hash(into hasher: inout Hasher) {
hasher.combine(definition)
}
var mockableScopedName: String {
return context.createScopedName(with: [], genericTypeContext: [], suffix: "Mock")
}

var classInitializerProxy: [Initializer] {
var classInitializerProxy: String? {
guard method.isInitializer,
isClassBound || !context.containsOverridableDesignatedInitializer
else { return [] }
else { return nil }
// We can't usually infer what concrete arguments to pass to the designated initializer.
guard !method.attributes.contains(.convenience) else { return [] }
guard !method.attributes.contains(.convenience) else { return nil }
let attributes = declarationAttributes.isEmpty ? "" : "\(declarationAttributes)\n"
let failable = method.attributes.contains(.failable) ? "?" : ""
let scopedName = context.createScopedName(with: [], genericTypeContext: [], suffix: "Mock")
let scopedName = mockableScopedName

let initializationLogic = """
return """
\(attributes)public static func \(fullNameForInitializerProxy)\(returnTypeAttributesForMocking) -> \(scopedName)\(failable)\(genericConstraints) {
let mock: \(scopedName)\(failable) = \(tryInvocation)\(scopedName)(\(superCallParameters))
mock\(failable).sourceLocation = SourceLocation(__file, __line)
"""

// Implicit mock type declarations, e.g. `let mock = mock(Bird.self).initialize(...)`
let implicitMockTypeCreatorDefinition = """
\(fullNameForImplicitInitializerProxy)\(returnTypeAttributesForMocking) -> \(context.abstractMockProtocolName)\(failable)\(genericConstraints)
"""
let implicitMockTypeCreator = """
\(attributes)public func \(implicitMockTypeCreatorDefinition) {
\(initializationLogic)
return mock
}
"""

// Explicit mock type declarations, e.g. `let mock: BirdMock = mock(Bird.self).initialize(...)`
let explicitMockTypeCreatorDefinition = """
\(fullNameForExplicitInitializerProxy)\(returnTypeAttributesForMocking) -> \(Constants.genericMockTypeName)\(failable)\(genericConstraints)
"""
let explicitMockTypeCreator = """
\(attributes)public func \(explicitMockTypeCreatorDefinition) {
\(initializationLogic)
return (mock as! \(Constants.genericMockTypeName))
}
"""

// Dummy object type declarations, e.g. `let dummy: Bird = dummy(Bird.self).initialize(...)`
let dummyObjectTypeCreatorDefinition = """
\(fullNameForDummyInitializerProxy)\(returnTypeAttributesForMocking) -> \(scopedName)\(failable)\(genericConstraints)
"""
let dummyObjectTypeCreator = """
\(attributes)public func \(dummyObjectTypeCreatorDefinition) {
\(initializationLogic)
return mock
}
"""

// Coerced mock type declarations, e.g. `let mock: Bird = mock(Bird.self).initialize(...)`
let coercedMockTypeCreatorDefinition = """
\(fullNameForUnavailableInitializerProxy)\(returnTypeAttributesForMocking) -> \(Constants.genericMockTypeName)\(failable)\(genericConstraints)
"""
let coercedMockTypeCreator = """
@available(swift, obsoleted: 3.0, message: "Store the mock in a variable of type '\(scopedName)' or use 'dummy(\(scopedName)\(genericConstraints).self).initialize(...)' to create a non-mockable dummy object")
\(attributes)public func \(coercedMockTypeCreatorDefinition) { fatalError() }
"""

return [Initializer(definition: implicitMockTypeCreatorDefinition,
body: implicitMockTypeCreator,
style: .implicit),
Initializer(definition: explicitMockTypeCreatorDefinition,
body: explicitMockTypeCreator,
style: .explicit),
Initializer(definition: dummyObjectTypeCreatorDefinition,
body: dummyObjectTypeCreator,
style: .dummy),
Initializer(definition: coercedMockTypeCreatorDefinition,
body: coercedMockTypeCreator,
style: .unavailable)]
}

var mockedDeclarations: String {
Expand Down Expand Up @@ -276,7 +212,7 @@ class MethodTemplate: Template {
enum FullNameMode {
case mocking
case matching(useVariadics: Bool)
case initializerProxy(style: InitializationStyle)
case initializerProxy

var isMatching: Bool {
switch self {
Expand All @@ -285,24 +221,24 @@ class MethodTemplate: Template {
}
}

var useVariadics: Bool {
var isInitializerProxy: Bool {
switch self {
case .matching(let useVariadics): return useVariadics
case .mocking, .initializerProxy: return false
case .matching, .mocking: return false
case .initializerProxy: return true
}
}

var initializationStyle: InitializationStyle? {
var useVariadics: Bool {
switch self {
case .mocking, .matching: return nil
case .initializerProxy(let style): return style
case .matching(let useVariadics): return useVariadics
case .mocking, .initializerProxy: return false
}
}
}

func shortName(for mode: FullNameMode) -> String {
let failable: String
if mode.initializationStyle != nil {
if mode.isInitializerProxy {
failable = ""
} else if method.attributes.contains(.failable) {
failable = "?"
Expand All @@ -317,23 +253,10 @@ class MethodTemplate: Template {
&& method.shortName.first?.isNumber != true
&& method.shortName.first != "_")
? "" : "`"
let shortName = mode.initializationStyle != nil ? "initialize" :
let shortName = mode.isInitializerProxy ? "initialize" :
(tick + method.shortName + tick)

var genericTypes = self.genericTypesList
if let style = mode.initializationStyle {
switch style {
case .implicit:
break
case .explicit:
genericTypes.append(Constants.genericMockTypeName + ": " + context.abstractMockProtocolName)
case .dummy:
break
case .unavailable:
genericTypes.append(Constants.genericMockTypeName)
}
}
let allGenericTypes = genericTypes.joined(separator: ", ")
let allGenericTypes = self.genericTypesList.joined(separator: ", ")

return genericTypes.isEmpty ?
"\(shortName)\(failable)" : "\(shortName)\(failable)<\(allGenericTypes)>"
Expand All @@ -345,20 +268,11 @@ class MethodTemplate: Template {
/// one variadic parameter, we can generate one method for wildcard matching using an argument
/// matcher, and another for specific matching using variadics.
lazy var fullNameForMatchingVariadics: String = { fullName(for: .matching(useVariadics: true)) }()
lazy var fullNameForImplicitInitializerProxy: String = {
return fullName(for: .initializerProxy(style: .implicit))
}()
lazy var fullNameForExplicitInitializerProxy: String = {
return fullName(for: .initializerProxy(style: .explicit))
}()
lazy var fullNameForDummyInitializerProxy: String = {
return fullName(for: .initializerProxy(style: .dummy))
}()
lazy var fullNameForUnavailableInitializerProxy: String = {
return fullName(for: .initializerProxy(style: .unavailable))
lazy var fullNameForInitializerProxy: String = {
return fullName(for: .initializerProxy)
}()
func fullName(for mode: FullNameMode) -> String {
let additionalParameters = mode.initializationStyle == nil ? [] :
let additionalParameters = !mode.isInitializerProxy ? [] :
["__file: StaticString = #file", "__line: UInt = #line"]
let parameterNames = method.parameters.map({ parameter -> String in
let typeName: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ struct MockableTypeInitializerTemplate: Template {
let nestedContainingTypeNames = containingTypeNames + [mockableTypeTemplate.mockableType.name]
let initializers = [renderInitializer(with: containingTypeNames)] +
mockableTypeTemplate.mockableType.containedTypes.map({ type -> String in
let typeNamePrefix = mockableTypeTemplate.abstractMockProtocolName
let template = MockableTypeInitializerTemplate(
mockableTypeTemplate: MockableTypeTemplate(mockableType: type,
abstractTypeNamePrefix: typeNamePrefix),
mockableTypeTemplate: MockableTypeTemplate(mockableType: type),
containingTypeNames: nestedContainingTypeNames
)
return template.render()
Expand All @@ -37,11 +35,6 @@ struct MockableTypeInitializerTemplate: Template {
.joined(separator: "\n\n")
}

private enum Constants {
static let genericMockTypeName = "__ReturnType"
static let anyInitializerProxyTypeName = "Mockingbird.Initializable"
}

private var requiresGenericInitializer: Bool {
let mockableType = mockableTypeTemplate.mockableType
let isSelfConstrainedProtocol = mockableType.kind == .protocol && mockableType.hasSelfConstraint
Expand Down Expand Up @@ -91,12 +84,6 @@ struct MockableTypeInitializerTemplate: Template {
let returnType: String
let returnExpression: String
let returnTypeDescription: String
let implicitReturnType: String
let dummyReturnType: String
let dummyReturnExpression: String

let implicitMockTypeCreatorAttributes: String
let coercedMockTypeCreatorAttributes: String

let mockTypeScopedName =
mockableTypeTemplate.createScopedName(with: containingTypeNames,
Expand All @@ -105,92 +92,24 @@ struct MockableTypeInitializerTemplate: Template {

if !mockableTypeTemplate.shouldGenerateDefaultInitializer {
// Requires an initializer proxy to create the partial class mock.
returnType = "\(mockTypeScopedName).InitializerProxy"
returnExpression = "\(returnType)()"
returnType = "\(mockTypeScopedName).InitializerProxy.Type"
returnExpression = "\(mockTypeScopedName).InitializerProxy.self"
returnTypeDescription = "an initializable class mock"
dummyReturnType = "\(mockTypeScopedName).InitializerProxy.Dummy"
dummyReturnExpression = "\(dummyReturnType)()"
implicitReturnType = Constants.anyInitializerProxyTypeName

let unavailableMessage = """
Initialize this class mock using 'mock(\(mockTypeScopedName).self).initialize(...)'
"""
coercedMockTypeCreatorAttributes = """
@available(swift, obsoleted: 3.0, message: "\(unavailableMessage)")
"""
implicitMockTypeCreatorAttributes = """
@available(*, deprecated, message: "\(unavailableMessage)")
"""
} else {
// Does not require an initializer proxy.
returnType = mockableTypeTemplate.abstractMockProtocolName
returnType = mockTypeScopedName
returnExpression = "\(mockTypeScopedName)(sourceLocation: SourceLocation(file, line))"
returnTypeDescription = "a " + (kind == .class ? "class" : "protocol") + " mock"
dummyReturnType = mockTypeScopedName
dummyReturnExpression = returnExpression
implicitReturnType = returnType

let mockedTypeScopedName =
mockableTypeTemplate.createScopedName(with: containingTypeNames,
genericTypeContext: genericTypeContext)
coercedMockTypeCreatorAttributes = """
@available(swift, obsoleted: 3.0, renamed: "dummy", message: "Store the mock in a variable of type '\(mockTypeScopedName)' or use 'dummy(\(mockedTypeScopedName).self)' to create a non-mockable dummy object")
"""
implicitMockTypeCreatorAttributes = ""
}

let allGenericTypes = genericTypeConstraints.isEmpty ? "" :
"<\(genericTypeConstraints.joined(separator: ", "))>"
let allGenericTypesWithSpecificReturnType = "<" + (
genericTypeConstraints + [Constants.genericMockTypeName + ": " + returnType]
).joined(separator: ", ") + ">"
let allGenericTypesWithWildcardReturnType = "<" + (
genericTypeConstraints + [Constants.genericMockTypeName]
).joined(separator: ", ") + ">"

let abstractMockType = """
public protocol \(mockableTypeTemplate.abstractMockProtocolName) {}
"""

let creatorDocumentation = """
return """
/// Initialize \(returnTypeDescription) of `\(mockableTypeTemplate.fullyQualifiedName)`.
"""

// Implicit mock type declarations, e.g. `let mock = mock(Bird.self)`
let implicitMockTypeCreator = """
\(creatorDocumentation)\(implicitMockTypeCreatorAttributes.isEmpty ? "" : "\n")\( implicitMockTypeCreatorAttributes)
public func mock\(allGenericTypes)(_ type: \(metatype), file: StaticString = #file, line: UInt = #line) -> \(implicitReturnType) {
public func mock\(allGenericTypes)(_ type: \(metatype), file: StaticString = #file, line: UInt = #line) -> \(returnType) {
return \(returnExpression)
}
"""

// Explicit mock type declarations, e.g. `let mock: BirdMock = mock(Bird.self)`
let explicitMockTypeCreator = """
\(creatorDocumentation)
public func mock\(allGenericTypesWithSpecificReturnType)(_ type: \(metatype), file: StaticString = #file, line: UInt = #line) -> \(Constants.genericMockTypeName) {
return \(returnExpression) as! \(Constants.genericMockTypeName)
}
"""

// Dummy object type declarations, e.g. `let dummy: Bird = dummy(Bird.self)`
let dummyObjectTypeCreator = """
/// Create a dummy object of `\(mockableTypeTemplate.fullyQualifiedName)`.
public func dummy\(allGenericTypes)(_ type: \(metatype), file: StaticString = #file, line: UInt = #line) -> \(dummyReturnType) {
return \(dummyReturnExpression)
}
"""

// Coerced mock type declarations, e.g. `let mock: Bird = mock(Bird.self)`
let coercedMockTypeCreator = """
\(creatorDocumentation)
\(coercedMockTypeCreatorAttributes)
public func mock\(allGenericTypesWithWildcardReturnType)(_ type: \(metatype)) -> \(Constants.genericMockTypeName) { fatalError() }
"""

return [abstractMockType,
implicitMockTypeCreator,
explicitMockTypeCreator,
dummyObjectTypeCreator,
coercedMockTypeCreator].joined(separator: "\n\n")
}
}
Loading

0 comments on commit 7f8638b

Please sign in to comment.