Skip to content

Commit

Permalink
[feat]: Rework FileManager + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkodes committed Feb 1, 2024
1 parent 0a10d96 commit fe1b1df
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 75 deletions.
2 changes: 1 addition & 1 deletion Sources/Networking/Utils/FileDataWriter.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// FileWriter.swift
// FileDataWriter.swift
//
//
// Created by Jan Kodeš on 24.01.2024.
Expand Down
117 changes: 65 additions & 52 deletions Tests/NetworkingTests/EndpointRequestStorageProcessorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import XCTest

final class EndpointRequestStorageProcessorTests: XCTestCase {
private let sessionId = "sessionId_request_storage"
private let fileManager = MockFileManager()
private let mockFileManager = MockFileManager()

struct MockBody: Codable {
let parameter: String
Expand Down Expand Up @@ -76,7 +76,7 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {
}

override func tearDown() {
fileManager.reset()
mockFileManager.reset()

super.tearDown()
}
Expand All @@ -90,7 +90,38 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {
let response = try await EndpointRequestStorageProcessor().process(mockResponse, with: mockURLRequest, for: mockEndpointRequest)

// test storing data processor doesn't effect response in anyway
XCTAssert(response.data == mockResponse.0 && response.response == mockResponse.1)
XCTAssertEqual(response.data, mockResponse.0)
XCTAssertEqual(response.response, mockResponse.1)
}

func testProcessCreatesCorrectFolder() async throws {
let mockEndpointRequest = EndpointRequest(MockRouter.testStoringGet, sessionId: sessionId)
let mockURLRequest = URLRequest(url: MockRouter.testStoringGet.baseURL)
let mockURLResponse: URLResponse = HTTPURLResponse(url: MockRouter.testStoringGet.baseURL, statusCode: 200, httpVersion: nil, headerFields: nil)!
let mockResponse = (Data(), mockURLResponse)

let expectation = expectation(description: "Data was written")

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let mockFileDataWriter = MockFileDataWriter()
mockFileDataWriter.writeClosure = {
expectation.fulfill()
}

let processor = EndpointRequestStorageProcessor(
fileManager: mockFileManager,
fileDataWriter: mockFileDataWriter,
jsonEncoder: encoder
)
_ = try await processor.process(mockResponse, with: mockURLRequest, for: mockEndpointRequest)

await fulfillment(of: [expectation], timeout: 20)

mockFileManager.verifyFunctionCall(.fileExists(path: responsesDirectory(for: mockEndpointRequest).path))
mockFileManager.verifyFunctionCall(.createDirectory(path: responsesDirectory(for: mockEndpointRequest).path))

XCTAssertEqual(mockFileDataWriter.receivedURL, fileUrl(for: mockEndpointRequest))
}

func testStoredDataForGetRequestWithJSONResponse() async throws {
Expand All @@ -108,28 +139,22 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let fileDataWriterSpy = MockFileDataWriter()
fileDataWriterSpy.writeClosure = {
let mockFileDataWriter = MockFileDataWriter()
mockFileDataWriter.writeClosure = {
expectation.fulfill()
}

let processor = EndpointRequestStorageProcessor(
fileManager: fileManager,
fileDataWriter: fileDataWriterSpy,
fileManager: mockFileManager,
fileDataWriter: mockFileDataWriter,
jsonEncoder: encoder
)
_ = try await processor.process(mockResponse, with: mockURLRequest, for: mockEndpointRequest)

await fulfillment(of: [expectation], timeout: 20)

let fileUrl = fileUrl(for: mockEndpointRequest)

guard let data = fileManager.contents(atPath: fileUrl.path) else {
XCTAssert(false, "File doesn't exist")
return
}

let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: data)
let receivedData = try XCTUnwrap(mockFileDataWriter.receivedData)
let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: receivedData)

XCTAssertEqual(model.statusCode, 200)
XCTAssertEqual(model.method, "GET")
Expand Down Expand Up @@ -165,31 +190,25 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {
let mockResponse = (mockResponseData, mockURLResponse)
let expectation = expectation(description: "Data was written")

let fileDataWriterSpy = MockFileDataWriter()
fileDataWriterSpy.writeClosure = {
let mockFileDataWriter = MockFileDataWriter()
mockFileDataWriter.writeClosure = {
expectation.fulfill()
}

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let processor = EndpointRequestStorageProcessor(
fileManager: fileManager,
fileDataWriter: fileDataWriterSpy,
fileManager: mockFileManager,
fileDataWriter: mockFileDataWriter,
jsonEncoder: encoder
)
_ = try await processor.process(mockResponse, with: mockURLRequest, for: mockEndpointRequest)

await fulfillment(of: [expectation], timeout: 20)

let fileUrl = fileUrl(for: mockEndpointRequest)

guard let data = fileManager.contents(atPath: fileUrl.path) else {
XCTAssert(false, "File doesn't exist")
return
}

let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: data)
let receivedData = try XCTUnwrap(mockFileDataWriter.receivedData)
let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: receivedData)

XCTAssertEqual(model.statusCode, 200)
XCTAssertEqual(model.method, "GET")
Expand Down Expand Up @@ -222,33 +241,28 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {

let expectation = expectation(description: "Data was written")

let fileDataWriterSpy = MockFileDataWriter()
fileDataWriterSpy.writeClosure = {
let mockFileDataWriter = MockFileDataWriter()
mockFileDataWriter.writeClosure = {
expectation.fulfill()
}

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let processor = EndpointRequestStorageProcessor(
fileManager: fileManager,
fileDataWriter: fileDataWriterSpy,
fileManager: mockFileManager,
fileDataWriter: mockFileDataWriter,
jsonEncoder: encoder
)

_ = await processor.process(mockError, for: mockEndpointRequest)

await fulfillment(of: [expectation], timeout: 20)

let fileUrl = fileUrl(for: mockEndpointRequest)
let receivedData = try XCTUnwrap(mockFileDataWriter.receivedData)

let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: receivedData)

guard let data = fileManager.contents(atPath: fileUrl.path) else {
XCTAssert(false, "File doesn't exist")
return
}

let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: data)

XCTAssertEqual(model.statusCode, 404)
XCTAssertEqual(model.method, "GET")
XCTAssertEqual(model.path, mockEndpointRequest.endpoint.path)
Expand All @@ -275,31 +289,26 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {

let expectation = expectation(description: "Data was written")

let fileDataWriterSpy = MockFileDataWriter()
fileDataWriterSpy.writeClosure = {
let mockFileDataWriter = MockFileDataWriter()
mockFileDataWriter.writeClosure = {
expectation.fulfill()
}

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let processor = EndpointRequestStorageProcessor(
fileManager: fileManager,
fileDataWriter: fileDataWriterSpy,
fileManager: mockFileManager,
fileDataWriter: mockFileDataWriter,
jsonEncoder: encoder
)
_ = try await processor.process(mockResponse, with: mockURLRequest, for: mockEndpointRequest)

await fulfillment(of: [expectation], timeout: 20)

let fileUrl = fileUrl(for: mockEndpointRequest)
let receivedData = try XCTUnwrap(mockFileDataWriter.receivedData)

guard let data = fileManager.contents(atPath: fileUrl.path) else {
XCTAssert(false, "File doesn't exist")
return
}

let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: data)
let model = try JSONDecoder().decode(EndpointRequestStorageModel.self, from: receivedData)
let mockRequestBody = try mockEndpointRequest.endpoint.encodeBody()!

XCTAssertEqual(model.statusCode, 200)
Expand All @@ -326,10 +335,14 @@ final class EndpointRequestStorageProcessorTests: XCTestCase {

private extension EndpointRequestStorageProcessorTests {
func fileUrl(for endpointRequest: EndpointRequest) -> URL {
let responsesDirectory = fileManager.temporaryDirectory.appendingPathComponent("responses")
let fileName = "\(endpointRequest.sessionId)_\(endpointRequest.endpoint.identifier)_0"
return responsesDirectory(for: endpointRequest)
.appendingPathComponent("\(fileName).json")
}

func responsesDirectory(for endpointRequest: EndpointRequest) -> URL {
let responsesDirectory = mockFileManager.temporaryDirectory.appendingPathComponent("responses")
return responsesDirectory
.appendingPathComponent(endpointRequest.sessionId)
.appendingPathComponent("\(fileName).json")
}
}
63 changes: 41 additions & 22 deletions Tests/NetworkingTests/Mocks/MockFileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,62 @@ import XCTest

/// A subclass of `FileManager` where the file existence is based on a dictionary whose key is the file path.
final class MockFileManager: FileManager {
enum Function: Equatable {
case fileExists(path: String)
case createDirectory(path: String)
case contentsOfDirectory(path: String)
case removeItem(path: String)
}

/// Mocked or received data
var dataByFilePath: [String: Data] = [:]

/// The mocked results for the `attributesOfItem(atPath:)`.
private var attributesOfItemResults = [String: [FileAttributeKey: Any]]()
/// Received functions
private var functionCallHistory: [Function] = []

override func fileExists(atPath path: String) -> Bool {
dataByFilePath[path] != nil
recordCall(.fileExists(path: path))
return dataByFilePath[path] != nil
}

override func createFile(atPath path: String, contents data: Data?, attributes attr: [FileAttributeKey: Any]? = nil) -> Bool {
dataByFilePath[path] = data
return true
}
override func createDirectory(atPath path: String, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey: Any]? = nil) throws {
recordCall(.createDirectory(path: path))

override func removeItem(at URL: URL) throws {
dataByFilePath.removeValue(forKey: URL.path)
// Simulate directory creation by adding an empty entry
dataByFilePath[path] = Data()
}

override func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any] {
guard let attributes = attributesOfItemResults[path] else {
XCTFail("No mocked attributes for path \(path)")
return [:]
}
override func contentsOfDirectory(atPath path: String) throws -> [String] {
recordCall(.contentsOfDirectory(path: path))

return attributes
// Return file names in the specified directory
return dataByFilePath.keys
.filter { $0.hasPrefix(path) }
.map { $0.replacingOccurrences(of: path, with: "") }
}

func reset() {
dataByFilePath = [:]
override func removeItem(atPath path: String) throws {
recordCall(.removeItem(path: path))

dataByFilePath.removeValue(forKey: path)
}
}

// MARK: - Mocking

extension MockFileManager {
/// Sets the return value when `attributesOfItem(atPath:)` is called.
func whenRetrievingAttributesOfItem(atPath path: String, thenReturn: [FileAttributeKey: Any]) {
attributesOfItemResults[path] = thenReturn
private func recordCall(_ method: Function) {
functionCallHistory.append(method)
}

func verifyFunctionCall(_ expectedMethod: Function, file: StaticString = #file, line: UInt = #line) {
guard functionCallHistory.contains(where: { $0 == expectedMethod }) else {
XCTFail("Expected to have called \(expectedMethod). Received: \(functionCallHistory)", file: file, line: line)
print(functionCallHistory)
return
}
}

func reset() {
dataByFilePath = [:]
functionCallHistory = []
}
}

0 comments on commit fe1b1df

Please sign in to comment.