I am new to kotlin multiplatform library.
I wanted to make a simple HTTP get request and test if it works.
here is what I have so far.
this is in the commonMain package
import io.ktor.client.*
import io.ktor.client.request.*
object HttpCall {
private val client: HttpClient = HttpClient()
suspend fun request(url: String): String = client.get(url)
}
and here is my attempt to test
#Test
fun should_make_http_call() {
GlobalScope.launch {
val response = HttpCall.request("https://stackoverflow.com/")
println("Response: ->$response")
assertTrue { response.contains("Stack Overflow - Where Developers Learn") }
assertTrue { response.contains("text that does not exist on stackoverflow") }
}
Now, this should fail because of the second assert but it doesn't.
no matter what I do the test always passes.
and printing the response does not work either
what am I doing wrong here?
The test function will run in a single thread, and if the function ends without failing, the test passes. GlobalScope.launch starts an operation in a different thread. The main test thread will finish before the network calls get a chance to run.
You should be calling this with something like runBlocking, but testing coroutines in general, and ktor specifically, on Kotlin native, is not easy because there's no easy way to have the suspended function continue on your current thread.
I will not use the GlobalScope or the runBlocking because they are not really made for Unit Test. Instead, I will use runTest.
Here are the steps:
Check your build.gradle and make sure you do have under commontTest the lib 'kotlinx-coroutines-test' set
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Version.kotlinCoroutines}")
...
}
}
Then into your directory commonTest, create a file and run
#Test
fun should_make_http_call() = runTest {
val response = HttpCall.request("https://stackoverflow.com/")
println("Response: ->$response")
assertTrue { response.contains("Stack Overflow - Where Developers Learn") }
assertTrue { response.contains("text that does not exist on stackoverflow") }
}
Extra:
runTest does not handle exceptions very well, so if you are interested in making to catch any exceptions if happens. Change runTest for runReliableTest, You can get the code from this link https://github.com/Kotlin/kotlinx.coroutines/issues/1205#issuecomment-1238261240
Related
As the title says, currently I'm having issues using mockk when writing unit tests in commonTest in my KMM project.
In my shared module, I created a useCase class that uses the expected object class to do things like read and write files. But when I follow the guide book(https://notwoods.github.io/mockk-guidebook/docs/mocking/static/) to mock the read and write operations, according to the debug result, it does not seem to excute the result of my mock instead of excuting the real operation.
The part of the use case class:
class UseCase {
fun needToTest{
...
if(FileOperation.mvFile(scr,dest)){
...
}
...
}
}
The example file operation class:
expect object FileOperation {
fun rdFile(path: String): List<Path>?
fun mvFile(srcPath: String, destPath: String): Boolean?
....
}
android Part
actual object FileOperation {
......
actual fun mvFile(srcPath:String, destPath: String): Boolean? {
......
}
.......
}
ios part
actual object FileOperation {
.......
.......
}
The example mockk method what I used currently
val useCaseTest = mock<UseCase>()
mockkObject(FileOperation)
every{ FileOperation.mvFile(srcPathMock, any())} returns true
when{
useCaseTest.needToTest()
}
The dependency in the build.gradle of shared module:
implementation("io.mockk:mockk-common:1.12.2")
You can separate the dependencis of MockK.
I have used this for commonTest:
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation("io.insert-koin:koin-test:$koinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
implementation("io.mockk:mockk-common:1.12.1") //<== THIS FOR MOCKK
}
}
And this one for androidTest:
val androidTest by getting {
dependencies {
// without this, it cannot find "every", "any" and some other functions
implementation("io.mockk:mockk-jvm:1.13.2")
}
}
When you run the tests from Android Studio and JVM you are ready to go, but you need more stuff to be added to run them for iOS. Unfortunately, I have no solution for iOS at the time of this writing.
You can find this solution in my sample project.
I am fighting a loosing battle against Spock unit tests in my Grails application. I want to test async behavior and in order to get familiar with Spock's BlockingVariable I've written this simple sample test.
void "test a cool function of my app I will not tell you about"() {
given:
def waitCondition = new BlockingVariable(10000)
def runner = new Runnable() {
#Override
void run() {
Thread.sleep(5000)
waitCondition.set(true)
}
}
when:
new Thread(runner)
then:
true == waitCondition.get()
}
Unfortunately it is not a good thing, because otherwise it would come to an end. When I set a breakpoint at Thread.sleep() and debug the test, that breakpoint is never hit. What am I missing?
Your test is broken, because you don't actually run the thread you create. Instead:
when:
new Thread(runner)
you should do:
when:
new Thread(runner).run()
and then your test succeeds after approximately 5 seconds.
In my Kotlin JUnit tests, I want to start/stop embedded servers and use them within my tests.
I tried using the JUnit #Before annotation on a method in my test class and it works fine, but it isn't the right behaviour since it runs every test case instead of just once.
Therefore I want to use the #BeforeClass annotation on a method, but adding it to a method results in an error saying it must be on a static method. Kotlin doesn't appear to have static methods. And then the same applies for static variables, because I need to keep a reference to the embedded server around for use in the test cases.
So how do I create this embedded database just once for all of my test cases?
class MyTest {
#Before fun setup() {
// works in that it opens the database connection, but is wrong
// since this is per test case instead of being shared for all
}
#BeforeClass fun setupClass() {
// what I want to do instead, but results in error because
// this isn't a static method, and static keyword doesn't exist
}
var referenceToServer: ServerType // wrong because is not static either
...
}
Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the answers to commonly asked Kotlin topics are present in SO.
Your unit test class usually needs a few things to manage a shared resource for a group of test methods. And in Kotlin you can use #BeforeClass and #AfterClass not in the test class, but rather within its companion object along with the #JvmStatic annotation.
The structure of a test class would look like:
class MyTestClass {
companion object {
init {
// things that may need to be setup before companion class member variables are instantiated
}
// variables you initialize for the class just once:
val someClassVar = initializer()
// variables you initialize for the class later in the #BeforeClass method:
lateinit var someClassLateVar: SomeResource
#BeforeClass #JvmStatic fun setup() {
// things to execute once and keep around for the class
}
#AfterClass #JvmStatic fun teardown() {
// clean up after this class, leave nothing dirty behind
}
}
// variables you initialize per instance of the test class:
val someInstanceVar = initializer()
// variables you initialize per test case later in your #Before methods:
var lateinit someInstanceLateZVar: MyType
#Before fun prepareTest() {
// things to do before each test
}
#After fun cleanupTest() {
// things to do after each test
}
#Test fun testSomething() {
// an actual test case
}
#Test fun testSomethingElse() {
// another test case
}
// ...more test cases
}
Given the above, you should read about:
companion objects - similar to the Class object in Java, but a singleton per class that is not static
#JvmStatic - an annotation that turns a companion object method into a static method on the outer class for Java interop
lateinit - allows a var property to be initialized later when you have a well defined lifecycle
Delegates.notNull() - can be used instead of lateinit for a property that should be set at least once before being read.
Here are fuller examples of test classes for Kotlin that manage embedded resources.
The first is copied and modified from Solr-Undertow tests, and before the test cases are run, configures and starts a Solr-Undertow server. After the tests run, it cleans up any temporary files created by the tests. It also ensures environment variables and system properties are correct before the tests are run. Between test cases it unloads any temporary loaded Solr cores. The test:
class TestServerWithPlugin {
companion object {
val workingDir = Paths.get("test-data/solr-standalone").toAbsolutePath()
val coreWithPluginDir = workingDir.resolve("plugin-test/collection1")
lateinit var server: Server
#BeforeClass #JvmStatic fun setup() {
assertTrue(coreWithPluginDir.exists(), "test core w/plugin does not exist $coreWithPluginDir")
// make sure no system properties are set that could interfere with test
resetEnvProxy()
cleanSysProps()
routeJbossLoggingToSlf4j()
cleanFiles()
val config = mapOf(...)
val configLoader = ServerConfigFromOverridesAndReference(workingDir, config) verifiedBy { loader ->
...
}
assertNotNull(System.getProperty("solr.solr.home"))
server = Server(configLoader)
val (serverStarted, message) = server.run()
if (!serverStarted) {
fail("Server not started: '$message'")
}
}
#AfterClass #JvmStatic fun teardown() {
server.shutdown()
cleanFiles()
resetEnvProxy()
cleanSysProps()
}
private fun cleanSysProps() { ... }
private fun cleanFiles() {
// don't leave any test files behind
coreWithPluginDir.resolve("data").deleteRecursively()
Files.deleteIfExists(coreWithPluginDir.resolve("core.properties"))
Files.deleteIfExists(coreWithPluginDir.resolve("core.properties.unloaded"))
}
}
val adminClient: SolrClient = HttpSolrClient("http://localhost:8983/solr/")
#Before fun prepareTest() {
// anything before each test?
}
#After fun cleanupTest() {
// make sure test cores do not bleed over between test cases
unloadCoreIfExists("tempCollection1")
unloadCoreIfExists("tempCollection2")
unloadCoreIfExists("tempCollection3")
}
private fun unloadCoreIfExists(name: String) { ... }
#Test
fun testServerLoadsPlugin() {
println("Loading core 'withplugin' from dir ${coreWithPluginDir.toString()}")
val response = CoreAdminRequest.createCore("tempCollection1", coreWithPluginDir.toString(), adminClient)
assertEquals(0, response.status)
}
// ... other test cases
}
And another starting AWS DynamoDB local as an embedded database (copied and modified slightly from Running AWS DynamoDB-local embedded). This test must hack the java.library.path before anything else happens or local DynamoDB (using sqlite with binary libraries) won't run. Then it starts a server to share for all test classes, and cleans up temporary data between tests. The test:
class TestAccountManager {
companion object {
init {
// we need to control the "java.library.path" or sqlite cannot find its libraries
val dynLibPath = File("./src/test/dynlib/").absoluteFile
System.setProperty("java.library.path", dynLibPath.toString());
// TEST HACK: if we kill this value in the System classloader, it will be
// recreated on next access allowing java.library.path to be reset
val fieldSysPath = ClassLoader::class.java.getDeclaredField("sys_paths")
fieldSysPath.setAccessible(true)
fieldSysPath.set(null, null)
// ensure logging always goes through Slf4j
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.Slf4jLog")
}
private val localDbPort = 19444
private lateinit var localDb: DynamoDBProxyServer
private lateinit var dbClient: AmazonDynamoDBClient
private lateinit var dynamo: DynamoDB
#BeforeClass #JvmStatic fun setup() {
// do not use ServerRunner, it is evil and doesn't set the port correctly, also
// it resets logging to be off.
localDb = DynamoDBProxyServer(localDbPort, LocalDynamoDBServerHandler(
LocalDynamoDBRequestHandler(0, true, null, true, true), null)
)
localDb.start()
// fake credentials are required even though ignored
val auth = BasicAWSCredentials("fakeKey", "fakeSecret")
dbClient = AmazonDynamoDBClient(auth) initializedWith {
signerRegionOverride = "us-east-1"
setEndpoint("http://localhost:$localDbPort")
}
dynamo = DynamoDB(dbClient)
// create the tables once
AccountManagerSchema.createTables(dbClient)
// for debugging reference
dynamo.listTables().forEach { table ->
println(table.tableName)
}
}
#AfterClass #JvmStatic fun teardown() {
dbClient.shutdown()
localDb.stop()
}
}
val jsonMapper = jacksonObjectMapper()
val dynamoMapper: DynamoDBMapper = DynamoDBMapper(dbClient)
#Before fun prepareTest() {
// insert commonly used test data
setupStaticBillingData(dbClient)
}
#After fun cleanupTest() {
// delete anything that shouldn't survive any test case
deleteAllInTable<Account>()
deleteAllInTable<Organization>()
deleteAllInTable<Billing>()
}
private inline fun <reified T: Any> deleteAllInTable() { ... }
#Test fun testAccountJsonRoundTrip() {
val acct = Account("123", ...)
dynamoMapper.save(acct)
val item = dynamo.getTable("Accounts").getItem("id", "123")
val acctReadJson = jsonMapper.readValue<Account>(item.toJSON())
assertEquals(acct, acctReadJson)
}
// ...more test cases
}
NOTE: some parts of the examples are abbreviated with ...
Managing resources with before/after callbacks in tests, obviously, has it's pros:
Tests are "atomic". A test executes as a whole things with all the callbacks One won't forget to fire up a dependency service before the tests and shut it down after it's done. If done properly, executions callbacks will work on any environment.
Tests are self-contained. There is no external data or setup phases, everything is contained within a few test classes.
It has some cons too. One important of them is that it pollutes the code and makes the code violate single responsibility principle. Tests now not only test something, but perform a heavyweight initialization and resource management. It can be ok in some cases (like configuring an ObjectMapper), but modifying java.library.path or spawning another processes (or in-process embedded databases) are not so innocent.
Why not treat those services as dependencies for your test eligible for "injection", like described by 12factor.net.
This way you start and initialize dependency services somewhere outside of the test code.
Nowadays virtualization and containers are almost everywhere and most developers' machines are able to run Docker. And most of the application have a dockerized version: Elasticsearch, DynamoDB, PostgreSQL and so on. Docker is a perfect solution for external services that your tests need.
It can be a script that runs is run manually by a developer every time she wants to execute tests.
It can be a task run by build tool (e.g. Gradle has awesome dependsOn and finalizedBy DSL for defining dependencies). A task, of course, can execute the same script that developer executes manually using shell-outs / process execs.
It can be a task run by IDE before test execution. Again, it can use the same script.
Most CI / CD providers have a notion of "service" — an external dependency (process) that runs in parallel to your build and can be accessed via it's usual SDK / connector / API: Gitlab, Travis, Bitbucket, AppVeyor, Semaphore, …
This approach:
Frees your test code from initialization logic. Your tests will only test and do nothing more.
Decouples code and data. Adding a new test case can now be done by adding new data into dependency services with it's native toolset. I.e. for SQL databases you'll use SQL, for Amazon DynamoDB you'll use CLI to create tables and put items.
Is closer to a production code, where you obviously do not start those services when your "main" application starts.
Of course, it has it's flaws (basically, the statements I've started from):
Tests are not more "atomic". Dependency service must be started somehow prior test execution. The way it is started may be different in different environments: developer's machine or CI, IDE or build tool CLI.
Tests are not self-contained. Now your seed data may be even packed inside an image, so changing it may require rebuilding a different project.
I modified the code from here to look very much like the code I have and am trying to create Unit Tests for. While I would like to ask "what is the best way" to test this, I will be quite satisfied with any way to create a decent Unit Test! I looked at Spock, GroovyTestCase and GMock, while each of them may be quite capable of creating such a test I found the documentation for such an example lacking in all cases.
class Searcher {
def http = new HTTPBuilder('http://ajax.googleapis.com')
def _executeSearch = { searchQ -> {
req ->
uri.path = '/ajax/services/search/web'
uri.query = searchQ
requestContentType = URLENC
response.success = { resp, reader ->
assert resp.statusLine.statusCode == 200
reader.text
}
response.failure = { resp -> println "FAILURE! ${resp.properties}"
resp.statusLine.statusCode }
}
}
def executeSearch(query) {
// http.setHeaders(Accept: 'application/json') // I want JSON back, but this not important
http.request(GET, _executeGetCommand(query))
}
}
What I want/need to do is to mock 'http' in such a way that I can test:
1. uri.query is getting properly set via the passed in data
2. calls to response.success return mocked test data
3. calls to failure get executed and return the failure code
I am probably approaching this entirely incorrectly and will be open to the "right way" of going about unit testing such code. Please bear with me though as this is new to me!
I would use Spock mock's for your test case. They should be quite straightforward, since you don't need any actual network interaction during unit testing.
Spock mocks are documented pretty well in
the new spock docs
If you do want to test web services from the client side, check out geb, which works well as a companion to spock. Geb is documented in the Book of Geb Integration testing over the web obviously involves more moving parts, so you're right to start by mocking out the server side.
What is the correct way to write a unit test for a synchronous method calling async methods.
Right now my unit test are passing, but when I try to open the page, it never returns.
Why isn't my unit test failing? How can I make it fail?
I replicated my problem with this simple code:
My passing test:
[TestMethod]
public void DoSomeWork_WhenWeDoSomeWork_ShouldReturnDone()
{
var service = new SyncService();
const string expected = "Done";
var actual = service.DoSomeWork();
Assert.AreEqual(expected, actual);
}
My view that never returns:
public ActionResult Index()
{
var syncService = new SyncService();
return View((object)syncService.DoSomeWork());
}
My service that never returns to view:
public class SyncService
{
public string DoSomeWork()
{
return SomeWork().GetAwaiter().GetResult();
}
private async Task<string> SomeWork()
{
var task1 = Task.Delay(1000);
var task2 = Task.Delay(1000);
await Task.WhenAll(task1, task2);
return "Done";
}
}
I don't think I can help you with this specific example, but I think a good general strategy is to write two tests. One to test if the synchronous method passes the correct data and an other to test if the asynchronous method works properly.
I mostly work in JavaScript and that general approach works for me. Also you can check the documentation of your testing frameworks, maybe it provides some methods for this.
First, don't block on async code (link to my blog). By blocking on async code, you're actually causing a deadlock. This deadlock does not happen in your unit test because unit tests run in a thread pool context, not an ASP.NET context (link to my blog).
There are good reasons for not having synchronous wrappers for asynchronous methods. So I recommend getting rid of DoSomeWork completely, leaving only SomeWork (renamed to SomeWorkAsync).
To solve your problem, you should use asynchronous controller actions.