Now I'm getting `The test application has already ...
# ktor
d
Now I'm getting
The test application has already been built. Make sure you configure the application before accessing the client for the first time.
🤕... I wish Ktor would just let me create my own
testApplication
without trying to magically find an existing one...
e
@Rustam Siniukov could you check?
d
This is my initialization code:
Copy code
testApplication {
                environment { this.config = MapApplicationConfig() }
                application {
                    this@testApplication.install(Authentication) { basic { skipWhen { true } } }

                    routing {
                        route("crm") {
                            registrationRoutes.externalRoutes(this)
                        }
                    }
                }
...
(I added the environment lambda in hope that it would override any "automatic settings"... but that doesn't work either)
And my main:
Copy code
fun main(args: Array<String>) {
    val env = applicationEngineEnvironment {
        module {
            val component = DaggerAppComponent.create()

            <http://component.app|component.app>(this)
        }

        // Private API
        connector {
            host = "0.0.0.0"
            port = 8080
        }

        // Public API
        connector {
            host = "0.0.0.0"
            port = 8050
        }
    }

    embeddedServer(Netty, env).start(true)
}
(but it's in a different gradle module than the test...)
I had to add:
fun Application.module() {}
to the source in the module where the test code is... maybe that's what's causing the problem?
But even removing that now still fails with the same error...
It finally worked with:
Copy code
testApplication {
                environment {
                    config = MapApplicationConfig()

                    module {
                        this@module.install(Authentication) { basic("myBasicAuth") { skipWhen { true } } }

                        routing {
                            route("crm") {
                                registrationRoutes.externalRoutes(this)
                            }
                        }
                    }
                }
But this REALLY wasn't obvious from the docs... that you need to use only ONE of the blocks environment, application, or routes in testApplication...
a
Could you please tell me what was the exact problem?
d
I was using environment block together with the application or routing one, but that tried instantiating more than one server rather than adding to the current one's configuration like I expected...
And the docs don't have such an example
Also, the install function was coming from the outer context in certain cases and was also trying to check for double servers and crashing the tests
a
Could you please share the code for a test where Ktor tries to instantiate more than one server?
The other is when using more than one of
application { }
,
environment { }
, or
routing { }
in the same
testApplication { }
block
r
why do you need
this@testApplication.
in the example above?
d
I expected them to be cumulative in building ONE server... but they each try to build a separate one. And there's no other error than the cryptic one I got before...
why do you need
this@testApplication.
in the example above?
I didn't, I just didn't know from where to take the
install()
function... so I needed to use trial and error... + the extra problem with multiple config blocks...
It ended up that I had to waste a lot of time just with migration to the new system... and it all started with the fact that there's too much magic in
testApplication { }
... it's hard to track down how it actually gets configured, when really I just needed a fresh instance to build myself... I don't really like having config files for that, since I need to inject fake fixtures inside my routing classes...
Do you think things could be made easier to set up @Rustam Siniukov? I think I'm probably not the only one that stumbled on a bunch of these problems, just the issue above already seems to point to a use-case that should really be more intuitive to configure for.
r
I don’t really understand you. You can use multiple blocks and it will configure one server. There is one detail about
testApplication
block that it will load your config file if you have one by default. It is explicitly listed in the doc and there is also example on how to disable it. Your problem here that you explicitly call function from the outer scope. You could just write
application { install(...) }
and it would work.
Or calling
install
in top level inside
testApplication { ... }
. But in your example you mixed these two.
d
Each one has checkIfNotBuilt:
Copy code
@KtorDsl
    public fun environment(block: ApplicationEngineEnvironmentBuilder.() -> Unit) {
        checkNotBuilt()
        environmentBuilder = block
    }

    /**
     * Adds a module to [TestApplication].
     * @see [testApplication]
     */
    @KtorDsl
    public fun application(block: Application.() -> Unit) {
        checkNotBuilt()
        applicationModules.add(block)
    }
...
It seemed to have crashed there when I tried multiple blocks
r
Yes, it’s needed so that users can’t try to add more modules after the app is already configured. If you check where
built
flag is set, you’ll see that it happens only in accessing environment, not on the install. Again, your code crashed because you explicitly called outer function in the inner scopo
d
Oh...
install()
from the outer scope does try to access the environment...?
r
No, it’s not. If simplified, top level
install(…)
is a shortcut for
application { install(...) }
. So when you call ``application { this@testApplication.install(...) }` it is the same as
application { application { install(...) } }
. We can support it by simple fix, but we haven’t seen this usage before.
262 Views