diff --git a/UnitTests.xcodeproj/project.pbxproj b/UnitTests.xcodeproj/project.pbxproj index 0b36676..598f222 100644 --- a/UnitTests.xcodeproj/project.pbxproj +++ b/UnitTests.xcodeproj/project.pbxproj @@ -13,9 +13,12 @@ B18A153528C3DD6600D23165 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B18A153328C3DD6600D23165 /* Main.storyboard */; }; B18A153728C3DD6900D23165 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B18A153628C3DD6900D23165 /* Assets.xcassets */; }; B18A153A28C3DD6900D23165 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B18A153828C3DD6900D23165 /* LaunchScreen.storyboard */; }; - B18A154528C3DD6900D23165 /* UnitTestsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A154428C3DD6900D23165 /* UnitTestsTests.swift */; }; B18A154F28C3DD6900D23165 /* UnitTestsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A154E28C3DD6900D23165 /* UnitTestsUITests.swift */; }; B18A155128C3DD6900D23165 /* UnitTestsUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A155028C3DD6900D23165 /* UnitTestsUITestsLaunchTests.swift */; }; + B1AA319128C4B653006FB275 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AA319028C4B653006FB275 /* Player.swift */; }; + B1AA319328C4BB2A006FB275 /* PlayerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AA319228C4BB2A006FB275 /* PlayerUnitTests.swift */; }; + B1AA319528C4CB3B006FB275 /* AsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AA319428C4CB3B006FB275 /* AsyncTests.swift */; }; + B1AA319728C4D0AF006FB275 /* URlSessionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AA319628C4D0AF006FB275 /* URlSessionMocks.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,10 +48,13 @@ B18A153928C3DD6900D23165 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B18A153B28C3DD6900D23165 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B18A154028C3DD6900D23165 /* UnitTestsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTestsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B18A154428C3DD6900D23165 /* UnitTestsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTestsTests.swift; sourceTree = ""; }; B18A154A28C3DD6900D23165 /* UnitTestsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTestsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B18A154E28C3DD6900D23165 /* UnitTestsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTestsUITests.swift; sourceTree = ""; }; B18A155028C3DD6900D23165 /* UnitTestsUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTestsUITestsLaunchTests.swift; sourceTree = ""; }; + B1AA319028C4B653006FB275 /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; }; + B1AA319228C4BB2A006FB275 /* PlayerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerUnitTests.swift; sourceTree = ""; }; + B1AA319428C4CB3B006FB275 /* AsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncTests.swift; sourceTree = ""; }; + B1AA319628C4D0AF006FB275 /* URlSessionMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URlSessionMocks.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -106,6 +112,8 @@ B18A153628C3DD6900D23165 /* Assets.xcassets */, B18A153828C3DD6900D23165 /* LaunchScreen.storyboard */, B18A153B28C3DD6900D23165 /* Info.plist */, + B1AA319028C4B653006FB275 /* Player.swift */, + B1AA319628C4D0AF006FB275 /* URlSessionMocks.swift */, ); path = UnitTests; sourceTree = ""; @@ -113,7 +121,8 @@ B18A154328C3DD6900D23165 /* UnitTestsTests */ = { isa = PBXGroup; children = ( - B18A154428C3DD6900D23165 /* UnitTestsTests.swift */, + B1AA319228C4BB2A006FB275 /* PlayerUnitTests.swift */, + B1AA319428C4CB3B006FB275 /* AsyncTests.swift */, ); path = UnitTestsTests; sourceTree = ""; @@ -260,6 +269,8 @@ files = ( B18A153228C3DD6600D23165 /* ViewController.swift in Sources */, B18A152E28C3DD6600D23165 /* AppDelegate.swift in Sources */, + B1AA319128C4B653006FB275 /* Player.swift in Sources */, + B1AA319728C4D0AF006FB275 /* URlSessionMocks.swift in Sources */, B18A153028C3DD6600D23165 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -268,7 +279,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B18A154528C3DD6900D23165 /* UnitTestsTests.swift in Sources */, + B1AA319528C4CB3B006FB275 /* AsyncTests.swift in Sources */, + B1AA319328C4BB2A006FB275 /* PlayerUnitTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/UnitTests/Assets.xcassets/AppIcon.appiconset/Contents.json b/UnitTests/Assets.xcassets/AppIcon.appiconset/Contents.json index 5a3257a..9221b9b 100644 --- a/UnitTests/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/UnitTests/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -70,6 +70,11 @@ "scale" : "2x", "size" : "40x40" }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, { "idiom" : "ipad", "scale" : "2x", diff --git a/UnitTests/Player.swift b/UnitTests/Player.swift new file mode 100644 index 0000000..7e5395d --- /dev/null +++ b/UnitTests/Player.swift @@ -0,0 +1,54 @@ +// +// Player.swift +// UnitTests +// +// Created by Nishchal Visavadiya on 04/09/22. +// + +import Foundation + +/** + * A model class to represent a player in the game + */ +class Player { + + private(set) var health = 100 + /** + * Represent the hit power of the plyer, + * i.e. how muhc damage this player can do in one hit + */ + private(set) var hitPower = 10 + /** + * Whether the player is dead or not + */ + var isDead: Bool { + get { + return health <= 0 + } + } + + /** + * Increments the `hitPower` of self + * + * - Parameter inc: The increment value + * + * - Returns: New hit power + */ + func increaseHitPowerBy(inc: Int) -> Int { + hitPower += inc + return hitPower + } + + /** + * Hits the playr passed in parameter with Player.hitPower + * + * - Parameter player: The player to hit + * + * - Returns: Whether the hit was successful or not + */ + func hit(player: Player) -> Bool { + if player.isDead { return false } + player.health -= hitPower + return true + } +} diff --git a/UnitTests/URlSessionMocks.swift b/UnitTests/URlSessionMocks.swift new file mode 100644 index 0000000..6ad7a5c --- /dev/null +++ b/UnitTests/URlSessionMocks.swift @@ -0,0 +1,72 @@ +// +// URlSessionMocks.swift +// UnitTests +// +// Created by Nishchal Visavadiya on 04/09/22. +// + +import Foundation + +typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void + +protocol URLSessionProtocol { + + associatedtype DataTaskType + + func dataTask( + with url: URL, + completionHandler: @escaping CompletionHandler + ) -> DataTaskType +} + +extension URLSession: URLSessionProtocol {} + +protocol URLSessionDataTaskProtocol { + func resume() +} + +extension URLSessionDataTask: URLSessionDataTaskProtocol {} + +class URLSessionDataTaskMock: URLSessionDataTaskProtocol { + + private let completion: CompletionHandler + private let data: Data? + private let error: Error? + private let response: URLResponse? + + init( + data: Data?, + error: Error?, + response: URLResponse?, + completion: @escaping CompletionHandler + ) { + self.data = data + self.error = error + self.response = response + self.completion = completion + } + + func resume() { + completion(data, response, error) + } +} + +class URLSessionMock: URLSessionProtocol { + + // data and error can be set to provide data or an error + var data: Data? + var error: Error? + var response: URLResponse? + + func dataTask( + with url: URL, + completionHandler: @escaping CompletionHandler + ) -> URLSessionDataTaskMock { + return URLSessionDataTaskMock( + data: data, + error: error, + response: response, + completion: completionHandler + ) + } +} diff --git a/UnitTestsTests/AsyncTests.swift b/UnitTestsTests/AsyncTests.swift new file mode 100644 index 0000000..5da8e0c --- /dev/null +++ b/UnitTestsTests/AsyncTests.swift @@ -0,0 +1,47 @@ +// +// AsyncTests.swift +// UnitTestsTests +// +// Created by Nishchal Visavadiya on 04/09/22. +// + +import XCTest +@testable import UnitTests + +class AsyncTests: XCTestCase { + + var sut: URLSessionMock! + + override func setUpWithError() throws { + try super.setUpWithError() + sut = URLSessionMock() + } + + func testApiCallCompletes() throws { + // given + let urlString = "http://www.randomnumberapi.com/test" + let url = URL(string: urlString)! + let promise = expectation(description: "Completion handler invoked") + var statusCode: Int? + var responseError: Error? + + // when + sut.response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) + let dataTask = sut.dataTask(with: url) { _, response, error in + statusCode = (response as? HTTPURLResponse)?.statusCode + responseError = error + promise.fulfill() + } + dataTask.resume() + wait(for: [promise], timeout: 5) + + // then + XCTAssertNil(responseError) + XCTAssertEqual(statusCode, 200) + } + + override func tearDownWithError() throws { + sut = nil + try super.tearDownWithError() + } +} diff --git a/UnitTestsTests/PlayerUnitTests.swift b/UnitTestsTests/PlayerUnitTests.swift new file mode 100644 index 0000000..4f00b4e --- /dev/null +++ b/UnitTestsTests/PlayerUnitTests.swift @@ -0,0 +1,62 @@ +// +// PlayerUnitTests.swift +// UnitTestsTests +// +// Created by Nishchal Visavadiya on 04/09/22. +// + +import Foundation +import XCTest +@testable import UnitTests + +class PlayerUnitTests: XCTestCase { + + private var sut: Player! + + override func setUpWithError() throws { + try super.setUpWithError() + sut = Player() + } + + func testHitPowerShouldIncrementBasedOnValueProvided() { + // Given + let oldHitPower = sut.hitPower + + // When + let newHitPower = sut.increaseHitPowerBy(inc: 10) + + // Then + XCTAssert(newHitPower == oldHitPower + 10, "Old hit power is incresed by value provided") + } + + func testPlayerCanHitAnotherOneIfNotDead() { + // Given + let anotherPlayer = Player() + XCTAssert(!anotherPlayer.isDead) + + // When + let hitSuccessfull = sut.hit(player: anotherPlayer) + + // Then + XCTAssert(hitSuccessfull == true, "Couuld no dealt damage") + } + + func testPlayerShouldBeDeadAfterHealtIsDepleted() { + // Given + let anotherPlayer = Player() + XCTAssert(!anotherPlayer.isDead) + + // When + _ = sut.increaseHitPowerBy(inc: 100) + let hitSuccessfull = sut.hit(player: anotherPlayer) + + // Then + XCTAssert(hitSuccessfull == true, "Could no dealt damage") + XCTAssert(anotherPlayer.isDead == true, "Player is not dead even after health less or equal to 0") + } + + override func tearDownWithError() throws { + sut = nil + try super.tearDownWithError() + } +} diff --git a/UnitTestsTests/UnitTestsTests.swift b/UnitTestsTests/UnitTestsTests.swift deleted file mode 100644 index e05eaef..0000000 --- a/UnitTestsTests/UnitTestsTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// UnitTestsTests.swift -// UnitTestsTests -// -// Created by Nishchal Visavadiya on 04/09/22. -// - -import XCTest -@testable import UnitTests - -class UnitTestsTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -}