Friday, June 21, 2024
HomeIOS DevelopmentOperating and testing async Vapor instructions

Operating and testing async Vapor instructions


How you can run async instructions in Vapor?

The async / await characteristic is comparatively new in Swift and a few framework authors have not transformed the whole lot to make the most of these new key phrases. At present, that is the scenario with the Command API in Vapor 4. You’ll be able to already outline async instructions, however there isn’t any option to register them utilizing the Vapor framework. Happily, there’s a comparatively easy workaround that you need to use if you wish to execute instructions utilizing an asynchronous context. 🔀

First we will outline a helper protocol and create an asyncRun operate. We’re going to lengthen the unique Command protocol and supply a default implementation for the run technique.

import Vapor

public protocol AsyncCommand: Command {
    
    func asyncRun(
        utilizing context: CommandContext,
        signature: Signature
    ) async throws
}

public extension AsyncCommand {

    func run(
        utilizing context: CommandContext,
        signature: Signature
    ) throws {
        let promise = context
            .utility
            .eventLoopGroup
            .subsequent()
            .makePromise(of: Void.self)
        
        promise.completeWithTask {
            strive await asyncRun(
                utilizing: context,
                signature: signature
            )
        }
        strive promise.futureResult.wait()
    }
}

This manner you need to be capable to create a brand new async command and you need to implement the asyncRun technique if you wish to name some asynchronous Swift code.

import Vapor

last class MyAsyncCommand: AsyncCommand {
    
    static let identify = "async"
    
    let assist = "This command run asynchronously."

    struct Signature: CommandSignature {}

    func asyncRun(
        utilizing context: CommandContext,
        signature: Signature
    ) async throws {
        context.console.data("That is async.")
    }
}

It’s attainable to register the command utilizing the configure technique, you possibly can do that out by operating the swift run Run async snippet in case you are utilizing the usual Vapor template. 💧

import Vapor

public func configure(
    _ app: Utility
) throws {

    app.instructions.use(
        MyAsyncCommand(),
        as: MyAsyncCommand.identify
    )

    strive routes(app)
}

As you possibly can see it is a fairly neat trick, it is also talked about on GitHub, however hopefully we do not want this workaround for too lengthy and correct async command assist will arrive in Vapor 4.x.

Unit testing Vapor instructions

This matter has actually zero documentation, so I believed it will be good to let you know a bit about unit check scripts created through ConsoleKit. Initially we want a TestConsole that we will use to gather the output of our instructions. It is a shameless ripoff from ConsoleKit. 😅

import Vapor

last class TestConsole: Console {

    var testInputQueue: [String]
    var testOutputQueue: [String]
    var userInfo: [AnyHashable : Any]

    init() {
        self.testInputQueue = []
        self.testOutputQueue = []
        self.userInfo = [:]
    }

    func enter(isSecure: Bool) -> String {
        testInputQueue.popLast() ?? ""
    }

    func output(_ textual content: ConsoleText, newLine: Bool) {
        let line = textual content.description + (newLine ? "n" : "")
        testOutputQueue.insert(line, at: 0)
    }

    func report(error: String, newLine: Bool) {
        
    }

    func clear(_ kind: ConsoleClear) {
        
    }

    var dimension: (width: Int, top: Int) {
        (0, 0)
    }
}

Now contained in the check suite, you need to create a brand new utility occasion utilizing the check surroundings and configure it for testing functions. Then you need to provoke the command that you simply’d like to check and run it utilizing the check console. You simply must create a brand new context and a correct enter with the required arguments and the console.run operate will deal with the whole lot else.

@testable import App
import XCTVapor

last class AppTests: XCTestCase {
    
    func testCommand() throws {
        let app = Utility(.testing)
        defer { app.shutdown() }
        strive configure(app)
        
        let command = MyAsyncCommand()
        let arguments = ["async"]
        
        let console = TestConsole()
        let enter = CommandInput(arguments: arguments)
        var context = CommandContext(
            console: console,
            enter: enter
        )
        context.utility = app
        
        strive console.run(command, with: context)

        let output = console
            .testOutputQueue
            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
        
        let expectation = [
            "This is async."
        ]
        XCTAssertEqual(output, expectation)
    }
}

The great factor about this answer is that the ConsoleKit framework will mechanically parse the arguments, choices and the flags. You’ll be able to present these as standalone array components utilizing the enter arguments array (e.g. ["arg1", "--option1", "value1", "--flag1"]).

It’s attainable to check command teams, you simply have so as to add the particular command identify as the primary argument that you simply’d wish to run from the group and you may merely verify the output by way of the check console in case you are on the lookout for the precise command outcomes. 💪

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments