Working and testing async Vapor instructions

Spread the love



The way to run async instructions in Vapor?


The async / await characteristic is comparatively new in Swift and a few framework authors have not transformed every thing to benefit from these new key phrases. At the moment, that is the scenario with the Command API in Vapor 4. You may already outline async instructions, however there isn’t any method to register them utilizing the Vapor framework. Thankfully, there’s a comparatively simple workaround that you should use if you wish to execute instructions utilizing an asynchronous context. 🔀


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


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
            .software
            .eventLoopGroup
            .subsequent()
            .makePromise(of: Void.self)
        
        promise.completeWithTask {
            strive await asyncRun(
                utilizing: context,
                signature: signature
            )
        }
        strive promise.futureResult.wait()
    }
}


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


import Vapor

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

    struct Signature: CommandSignature {}

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


It’s doable to register the command utilizing the configure methodology, you possibly can do this 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.title
    )

    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 assumed it might be good to let you know a bit about find out how to unit take a look at scripts created by way of ConsoleKit. To begin with we want a TestConsole that we are able to use to gather the output of our instructions. This can be a shameless ripoff from ConsoleKit. 😅


import Vapor

closing 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 measurement: (width: Int, top: Int) {
        (0, 0)
    }
}


Now contained in the take a look at suite, you must create a brand new software occasion utilizing the take a look at surroundings and configure it for testing functions. Then you must provoke the command that you simply’d like to check and run it utilizing the take a look at console. You simply need to create a brand new context and a correct enter with the mandatory arguments and the console.run perform will care for every thing else.


@testable import App
import XCTVapor

closing 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.software = 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 good factor about this answer is that the ConsoleKit framework will routinely parse the arguments, choices and the flags. You may present these as standalone array components utilizing the enter arguments array (e.g. ["arg1", "--option1", "value1", "--flag1"]).


It’s doable to check command teams, you simply have so as to add the precise command title as the primary argument that you simply’d prefer to run from the group and you may merely verify the output by way of the take a look at console in case you are in search of the precise command outcomes. 💪




Leave a Reply

Your email address will not be published. Required fields are marked *