-
Notifications
You must be signed in to change notification settings - Fork 0
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
Additional Test Helpers #10
Conversation
/// } | ||
/// } | ||
/// ``` | ||
public struct ContextProvider<E> { |
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.
Just a nit that E
may be too concise for a "environment variable".
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.
Good point, especially when I have used EnvironmentVariable
elsewhere
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.
Looks like it is E
on MockContext
as well 🤷♂️
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.
Updated MockContext
and MockInitializationContext
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.
This does bring up some confusion on my part around why these types are generic. I realize this relates to code that is already approved/merged, however Lambda.env(_:)
takes a concrete String
type. Could you clarify the added flexibility?
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 basic idea is types > strings
Let's say I define 2 environment variables for my Lambda in my SAM template:
bar
baz
Using Lambda.env(_:)
the compiler would allow:
guard let bar = Lambda.env("barrr") else {
throw HandlerError.envError("bar")
}
With this I can do:
enum Environment: String {
/// Explanation of `bar`
case bar
/// Explanation of `baz`
case baz
}
extension LambdaInitializationContext: EnvironmentValueProvider {
public typealias EnvironmentVariable = Environment
}
extension LambdaContext: EnvironmentValueProvider {
public typealias EnvironmentVariable = Environment
}
...
let bar = try context.value(for: .bar)
While I do offer a LambdaExtras.DefaultEnvironment
type, in most cases there will be a project-specific Environment
type defined in a target using this package that represents the additional variables that are defined in the template or through the AWS console. The generics allow the contexts and other types to support those externally-defined types.
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.
That is supported by:
extension Lambda {
/// Returns the value of the environment varialbe with the given name.
///
/// This method throws ``EventHandler.envError`` if a value for the given environment variable
/// name is not found.
///
/// - Parameter name: The name of the environment variable to return.
/// - Returns: The value of the given environment variable.
static func env(name: String) throws -> String {
guard let value = env(name) else {
throw HandlerError.envError(name)
}
return value
}
}
public extension EnvironmentValueProvider where EnvironmentVariable: RawRepresentable<String> {
/// Returns the value of the given environment variable.
///
/// - Parameter environmentVariable: The environment variable whose value should be returned.
func value(for environmentVariable: EnvironmentVariable) throws -> String {
try Lambda.env(name: environmentVariable.rawValue)
}
}
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.
So, there are other ways to get around the issues I pointed to above, but more generally, this setup means that instead of:
import AWSLambdaRuntimeCore
struct Foo {
var stuff: (LambdaContext) async throws -> String
}
extension Foo: DependencyKey {
static var liveValue = Foo(
// To control environment variables we need to either export them before calling
// `swift test` or access them inside handler dependencies that we'll mock
// in tests
stuff: { context in
let bar = try Lambda.env(.bar)
.unwrap(or: "Could not find bar in env vars.")
... other stuff having nothing to do with `context`
}
)
}
public struct MyHandler {
@Dependency(\.foo) var foo
public func run(with event: Event, context: LambdaContext) async throws {
let stuff = try await foo.stuff(context)
// `LambdaContext` gets this by subtracting now from `.deadline` so
// we'd need to use delays to test
if context.getRemainingTime() > 100000 {
...
}
}
}
You can do:
import LambdaExtrasCore // No AWS dependencies
struct Foo {
// Doesn't need to know about the context
var stuff: (String) async throws -> String
}
public struct MyHandler {
@Dependency(\.foo) var foo
public func run<C>(
with event: Event,
context: C
) async throws -> String where C: RuntimeContext & EnvironmentValueProvider<Environment> {
/// Can just access this here and mock
let bar = try context.value(for: .bar)
let stuff = try await foo.stuff(bar)
/// Can easily mock this via `MockContext.remainingTimeProvider`
if context.getRemainingTime() > 100000 {
...
}
}
}
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.
Got it, I'm tracking better now. Thanks!
This includes multiple helpers intended to improve testing.
MockContext.Configuration
to make it easier to create / pass around the the context's dataLogger.mock
mocked()
functionsContextProvider
(not totally sure about the name) to abstract the creation of mock initialization and runtime contexts for testing and the management of their resources.Other:
RuntimeContext.getRemainingTime()
to fully representLambdaContext
.Demonstration of
ContextProvider
:The idea is to replace:
with:
An
XCTestCase
subclass would be an alternative, but that's not really something I've seen much of.