Unit test async scala code
Asked Answered
R

3

7

Experimenting with concurrent execution I was wondering how to actually test it. The execution flow is of a side-effect nature and futures are created to wrap independent executions/processing.

Been searching for some good examples on how to properly unit test the following scenarios (foo and bar are the methods I wish to test):

scenario #1

def foo : Unit = {
    Future { doSomething }
    Future { doSomethingElse }
}

private def doSomething : Unit = serviceCall1
private def doSomethingElse : Unit = serviceCall2

Scenario motivation

foo immediately returns but invokes 2 futures which perform separate tasks (e.g. save analytics and store record to DB). These service calls can be mocked, but what I'm trying to test is that both these services are called once I wrap them in Futures

scenario #2

def bar : Unit = {
    val futureX = doAsyncX
    val futureY = doAsyncY
    for {
        x <- futureX
        y <- futureY
    } yield {
        noOp(x, y)
    }
}

Scenario motivation

Start with long running computations that can be executed concurrently (e.g. get the number of total visitors and get the frequently used User-Agent header to our web site). Combine the result in some other operation (which in this case Unit method that simply throws the values)

Note I'm familiar with actors and testing actors, but given the above code I wonder what should be the most suitable approach (refactoring included)

EDIT What I'm doing at the moment

implicit value context = ExecutionContext.fromExecutor(testExecutor)

def testExecutor = {
    new Executor {
        def execute(runnable : Runnable) = runnable.run
    }
}

This ExecutionContext implementation will not run the Future as a separate thread and the entire execution will be done in sequence. This kinda feels like a hack but based on Electric Monk answer, it seems like the other solution is more of the same.

Reluctivity answered 6/8, 2014 at 19:48 Comment(9)
I deleted my answer, since it wasn't on topic, but you should really explain your problem more clearly.Sennar
@GabrielePetronella Thanks for the answer, and for the comment. I've edited my answer to (hopefully) better reflect my intentions.Reluctivity
the only thing needed to be tested is that foo makes a call on the 2 methods doSomething and doSomethingElse ? you are looking for a proof that they are called and dont care what they do ?Girandole
@Girandole correct. Scenario #1 tests that both services are called. Scenario #2 is more complex but noOp can be a mocked services that I wish to test if it was invoked as expectedReluctivity
doSomething and doSomethingElse have to be private methods ?Girandole
@Girandole They don't have to exist at all. It could also be Future { serviceCall1 }Reluctivity
To make sure I understand it correctly: the fundamental issue here is that there's nothing (future, wait handle etc.) you can actually wait on? In other words, you can mock the actual "service calls" but there's nowhere to stick the verification test?Azazel
@TomerGabel right, and I wouldn't like to use Thread.sleep() just to (falsely) wait for the futures and run the mock validation. What I'm doing now still feels wrong.Reluctivity
Other than switching to actually returning Futures (probably the better option), the only alternatives I see are to use a sequential executor (as you've done), or to hack your mock services mark a condition you can await in the test code.Azazel
J
2

One solution would be to use a DeterministicExecutor. Not a scalaesque solution, but should so the trick.

Jagannath answered 10/8, 2014 at 5:14 Comment(2)
I've added the solution I'm currently testing with. Which looks like the same approach as DeterministicExecutor. Isn't there a more cleaner way to test async execution?Reluctivity
How does this differ from calling ExecutorService.shutdown followed by ExecutorService.awaitTermination?Azazel
B
1

If you are using ScalaTest, take a look at: http://doc.scalatest.org/2.0/index.html#org.scalatest.concurrent.Futures

Specs2 also has support for testing Futures: http://etorreborre.github.io/specs2/guide/org.specs2.guide.Matchers.html

Blent answered 6/8, 2014 at 21:32 Comment(11)
I'm not testing Futures but async (concurrent) execution. Note that both these methods are Unit and don't return FutureReluctivity
However, Future { doSomething } does return a Future. That is the point of Venkat's response.Cori
@BobDalgleish but the method returns nothing (It can return Future[Unit] )Reluctivity
Okay, I'm starting to catch on now. In Scenario 1, you don't unit test foo, since it is essentially a shim or placeholder. Do you have unit tests for doSomething and doSomethingElse? If so, you can use the scalatest Futures to test Future { doSomething }, etc.Cori
@BobDalgleish I wish to test foo and bar. doSomething, doSomethingElse, doAsyncX, doAsyncY and noOp have their own unit tests (which is easy to test with ScalaTest and Specs2)Reluctivity
Again, you don't unit test foo as it is just a shim. If you are desperate to test it, you will need to isolate it from the definitions of doSomething and doSomethingElse so that you can mock them. Similarly for bar: create mocks of the procedures being called.Cori
Think of them as service calls. Now that I mock them, the future is still wrapping a Unit call. FooTest should simply test that both are invoked (order is irrelevant)Reluctivity
@Bivas: You cannot expect a method that returns Unit to return you Strings or Floats or Booleans if you wrap it in a Future. Your doSth returns Unit. If you want another type A, your doSth should return A to have Future[A]Blent
@venkat who said I'm returning anything? I'm simply invoking 2 Unit method and wrap them in futures. If it they were services, I could simply mock them. But how do I replicate the test scenario now that I have futures wrapping every call.Reluctivity
@Reluctivity Did you have a solution about your problem ? I would like to write integration tests on async method which return Unit.Goiter
@Goiter I went with the hack between my solution and Electric Monk suggestion. See my edit to the original answer.Reluctivity
C
0

ScalaTest 3.x supports asynchronous non-blocking testing.

Counterpunch answered 17/10, 2016 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.