XCTestHelpers.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /*
  2. * Copyright 2020, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import GRPC
  17. import XCTest
  18. struct UnwrapError: Error {}
  19. // We support Swift versions before 'XCTUnwrap' was introduced.
  20. func assertNotNil<Value>(
  21. _ expression: @autoclosure () throws -> Value?,
  22. message: @autoclosure () -> String = "Optional value was nil",
  23. file: StaticString = #file,
  24. line: UInt = #line
  25. ) throws -> Value {
  26. guard let value = try expression() else {
  27. XCTFail(message(), file: file, line: line)
  28. throw UnwrapError()
  29. }
  30. return value
  31. }
  32. func assertNoThrow<Value>(
  33. _ expression: @autoclosure () throws -> Value,
  34. message: @autoclosure () -> String = "Unexpected error thrown",
  35. file: StaticString = #file,
  36. line: UInt = #line
  37. ) throws -> Value {
  38. do {
  39. return try expression()
  40. } catch {
  41. XCTFail(message(), file: file, line: line)
  42. throw error
  43. }
  44. }
  45. // MARK: - Matchers.
  46. func assertThat<Value>(
  47. _ expression: @autoclosure @escaping () throws -> Value,
  48. _ matcher: Matcher<Value>,
  49. file: StaticString = #file,
  50. line: UInt = #line
  51. ) {
  52. // For value matchers we'll assert that we don't throw by default.
  53. assertThat(try expression(), .doesNotThrow(matcher), file: file, line: line)
  54. }
  55. func assertThat<Value>(
  56. _ expression: @autoclosure @escaping () throws -> Value,
  57. _ matcher: ExpressionMatcher<Value>,
  58. file: StaticString = #file,
  59. line: UInt = #line
  60. ) {
  61. switch matcher.evaluate(expression) {
  62. case .match:
  63. ()
  64. case let .noMatch(actual: actual, expected: expected):
  65. XCTFail("ACTUAL: \(actual), EXPECTED: \(expected)", file: file, line: line)
  66. }
  67. }
  68. enum MatchResult {
  69. case match
  70. case noMatch(actual: String, expected: String)
  71. }
  72. struct Matcher<Value> {
  73. private typealias Evaluator = (Value) -> MatchResult
  74. private var matcher: Evaluator
  75. private init(_ matcher: @escaping Evaluator) {
  76. self.matcher = matcher
  77. }
  78. fileprivate func evaluate(_ value: Value) -> MatchResult {
  79. return self.matcher(value)
  80. }
  81. // MARK: Sugar
  82. /// Just returns the provided matcher.
  83. static func `is`<Value>(_ matcher: Matcher<Value>) -> Matcher<Value> {
  84. return matcher
  85. }
  86. /// Just returns the provided matcher.
  87. static func and<Value>(_ matcher: Matcher<Value>) -> Matcher<Value> {
  88. return matcher
  89. }
  90. // MARK: Equality
  91. /// Checks the equality of the actual value against the provided value. See `equalTo(_:)`.
  92. static func `is`<Value: Equatable>(_ value: Value) -> Matcher<Value> {
  93. return .equalTo(value)
  94. }
  95. /// Checks the equality of the actual value against the provided value.
  96. static func equalTo<Value: Equatable>(_ expected: Value) -> Matcher<Value> {
  97. return .init { actual in
  98. actual == expected
  99. ? .match
  100. : .noMatch(actual: "\(actual)", expected: "equal to \(expected)")
  101. }
  102. }
  103. /// Always returns a 'match', useful when the expected value is `Void`.
  104. static func isVoid() -> Matcher<Void> {
  105. return .init {
  106. return .match
  107. }
  108. }
  109. /// Matches if the value is `nil`.
  110. static func `nil`<Value>() -> Matcher<Value?> {
  111. return .init { actual in
  112. actual == nil
  113. ? .match
  114. : .noMatch(actual: String(describing: actual), expected: "nil")
  115. }
  116. }
  117. /// Matches if the value is not `nil`.
  118. static func notNil<Value>() -> Matcher<Value?> {
  119. return .init { actual in
  120. actual != nil
  121. ? .match
  122. : .noMatch(actual: "nil", expected: "not nil")
  123. }
  124. }
  125. // MARK: Type
  126. /// Checks that the actual value is an instance of the given type.
  127. static func instanceOf<Value, Expected>(_: Expected.Type) -> Matcher<Value> {
  128. return .init { actual in
  129. if actual is Expected {
  130. return .match
  131. } else {
  132. return .noMatch(
  133. actual: String(describing: type(of: actual)) + " (\(actual))",
  134. expected: "value of type \(Expected.self)"
  135. )
  136. }
  137. }
  138. }
  139. // MARK: Collection
  140. /// Checks whether the collection has the expected count.
  141. static func hasCount<C: Collection>(_ count: Int) -> Matcher<C> {
  142. return .init { actual in
  143. actual.count == count
  144. ? .match
  145. : .noMatch(actual: "has count \(actual)", expected: "count of \(count)")
  146. }
  147. }
  148. // MARK: gRPC matchers
  149. static func hasCode(_ code: GRPCStatus.Code) -> Matcher<GRPCStatus> {
  150. return .init { actual in
  151. actual.code == code
  152. ? .match
  153. : .noMatch(actual: "has status code \(actual)", expected: "\(code)")
  154. }
  155. }
  156. }
  157. struct ExpressionMatcher<Value> {
  158. typealias Expression = () throws -> Value
  159. private typealias Evaluator = (Expression) -> MatchResult
  160. private var evaluator: Evaluator
  161. private init(_ evaluator: @escaping Evaluator) {
  162. self.evaluator = evaluator
  163. }
  164. fileprivate func evaluate(_ expression: Expression) -> MatchResult {
  165. return self.evaluator(expression)
  166. }
  167. /// Asserts that the expression does not throw and error. Returns the result of any provided
  168. /// matcher on the result of the expression.
  169. static func doesNotThrow<Value>(_ matcher: Matcher<Value>? = nil) -> ExpressionMatcher<Value> {
  170. return .init { expression in
  171. do {
  172. let value = try expression()
  173. return matcher?.evaluate(value) ?? .match
  174. } catch {
  175. return .noMatch(actual: "threw '\(error)'", expected: "should not throw error")
  176. }
  177. }
  178. }
  179. /// Asserts that the expression throws and error. Returns the result of any provided matcher
  180. /// on the error thrown by the expression.
  181. static func `throws`<Value>(_ matcher: Matcher<Error>? = nil) -> ExpressionMatcher<Value> {
  182. return .init { expression in
  183. do {
  184. let value = try expression()
  185. return .noMatch(actual: "returned '\(value)'", expected: "should throw error")
  186. } catch {
  187. return matcher?.evaluate(error) ?? .match
  188. }
  189. }
  190. }
  191. }