-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve HaversackEphemeralStrategy ergonomics #11
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The added unit tests show that the subscript code can get the values that are set with the subscript code, but they do not show that they coordinate exactly with the long key names. Can you please add one more test such as testGenericPWSearchReferenceMock()
where you set the mock data with the subscript, then call a Haversack function that will use the ephemeral strategy to return the mocked value?
Basically a copy of testGenericPWSearchReferenceMock()
but with line 36 changed to use the nicer query-based subscript instead of a long strange string.
Perhaps as you suspected @macblazer, writing this unit test revealed that the current implementation is functionally useless because
Some mostly real sample code to better visualize these options: // In the HaversackMock library
extension Haversack {
// Alternative 1
public func mockFirst<T: KeychainQuerying>(where query: T, value: Any) throws {
let query = try makeSearchQuery(query, singleItem: true)
let ephemeralStrategy = configuration.strategy as! HaversackEphemeralStrategy
let key = ephemeralStrategy.key(for: query)
ephemeralStrategy.mockData[key] = value
}
// Alternative 2
public func mock(value: Any, forErrorThrownFrom operation: () throws -> Void) rethrows {
let ephemeralStrategy = configuration.strategy as! HaversackEphemeralStrategy
do {
try operation()
} catch let error as NSError {
guard <error is "missing value for key" error> else {
throw error
}
ephemeralStrategy.mockData[<missing key>] = value
}
}
} // In unit tests
// Alternative 1
func testPasswordQuery() throws {
let query = GenericPasswordQuery()
let mock = HaversackEphemeralStrategy()
let haversack = Haversack(configuration: .init(strategy: mock))
let expectedEntity = GenericPasswordEntity()
try haversack.mockFirst(where: query, value: expectedEntity)
}
// Alternative 2
func testPasswordQuery() throws {
let query = GenericPasswordQuery()
let mock = HaversackEphemeralStrategy()
let haversack = Haversack(configuration: .init(strategy: mock))
let expectedEntity = GenericPasswordEntity()
try haversack.mock(value: expectedEntity) {
_ = try haversack.first(where: query)
_ = try haversack.first(where: KeyQuery()) // Invalid usage, this line will never run
}
let password = try haversack.first(where: pwQuery)
XCTAssertNotNil(password)
} I think I prefer alternative 1 because it has less room for developer error. As I called out in the test example for alternative 2, you can perform as many operations on your |
Alternative 1 is definitely better. The subscripting behavior can be removed, and some methods added to more easily set mock data into the strategy would be better. The second parameter might be better as Instead of the force typecast to the |
100%, the force cast was for simplicity while brainstorming |
Having recently written some unit tests using the ephemeral strategy, I found myself wishing that I didn't have to commit a bunch of difficult to read strings for repeatable access to the mock data. To address this, I've introduced a subscript to
HaversackEphemeralStrategy
so that developers can accessmockData
via the queries they already have.I did consider adding a pair of
setValue
/getValue
functions but thought that the subscript access would be more ergonomic, especially considering thatHaversackEphemeralStrategy
is essentially just a wrapper aroundmockData
(excluding key import/export).