#Mocking Request for Unit Tests

1 messages · Page 1 of 1 (latest)

fair grotto
#

Is there a correct way to mock a request instance for unit testing?

The initializer for Request requires an Application and EventLoop. Hmm 🤔

I looked at the implementation of XCTApplicationTester.test(...) and saw that it calls Application.InMemory.performTest(request:). The implementation of performTest(request:) instantiates a new Request. What looks interesting is that it passes app.eventLoopGroup.next() for the on: argument.

So, in my unit test ...

let mockRequest = Request(app, on: app.eventLoopGroup.next())

fooController.doStuff(mockRequest)
// more excellent test code

However, this generates a crash when Imperial is installed. In OAuthService on line 12, the services: ThreadSpecificVariable<OAuthServiceContainer>is nil for .currentValue. Obviously this is unexpected, which means my faked request instance was incorrect.

Hoping someone can point me on a better path.

small cape
#

Does it work if you use app.test?

fair grotto
#

@small cape it also crashes using try app.test... 😱

So, hmm.

I don't think there's anything fishy with my ImperialGoogle collection...

struct GoogleController: RouteCollection {
    
    func boot(routes: RoutesBuilder) throws
    {
        guard let callbackURL = Environment.get("GOOGLE_CALLBACK_URL") else {
            fatalError("Google callback URL not found in Environment")
        }
        
        try routes.oAuth(
            from: Google.self,
            authenticate: "login/google",
            callback: callbackURL,
            scope: [ "profile", "email" ],
            completion: login
        )
    }

Like I said, the crash happens in OAuthService.swift in ImperialCore:

import class NIO.ThreadSpecificVariable
import Vapor

fileprivate var services: ThreadSpecificVariable<OAuthServiceContainer> = .init(value: .init())

/// Represents a service that interacts with an OAuth provider.
public struct OAuthService: Codable, Content {

    /// The services that are available for use in the application.
    /// Services are added and fetched with the `Service.register` and `.get` static methods.
    private static var services: OAuthServiceContainer {
        get { ImperialCore.services.currentValue! }
        set { ImperialCore.services.currentValue  = newValue }
    }
...
small cape
#

Are you expecting the unit test to call out to the API btw?

#

My suggestion would be breakpoint and walk up the stack to find out what's going on

fair grotto
#

The stack trace is in my unit test in the setUp() method for the test case subclass.

Minimal example project here - https://github.com/evandelaney/ImperialTesting

I guess I will open an official bug on the Imperial repo with this info, too.

I'd like to contribute a bug fix, if possible, but I don't know what ThreadSpecificVariable is in NIOPosix. "It's like Netty, but written for Swift" is a context I don't have. 😬

GitHub

Contribute to evandelaney/ImperialTesting development by creating an account on GitHub.

#
/// Initialize a new `ThreadSpecificVariable` with `value` for the calling thread. After calling this, the calling
/// thread will see `currentValue == value` but on all other threads `currentValue` will be `nil` until changed.
///
/// - parameters:
///   - value: The value to set for the calling thread.

ooooh... 🤔

fair grotto
tall spindle
#

@fair grotto don't use ThreadSpecificVariable, since it's imcompatible with async/await

small cape
#

It's a thing in Imperial unfortunately

#

The whole package needs an update for a/a