I have a piece of code that goes something like this:
somePublisher
.subscribeOn(...)
.flatMap { x -> someFunctionThatReturnsMono(x) }
.retry(3)
.subscribe()
So far I have managed to unit test happy paths, like whether the code inside map {...} is getting called, using the tools from reactor-test.
Now I want to test errors and retries. How can I test to make sure someFunctionThatReturnsMono(x) is getting called at most 4 times when consecutive errors occur?
Simple way is to mock function and count its calls:
def 'Test retry'(){
setup:
someBean = Mock(SomeBean)
def testMono = Mono.just(X)
.flatMap { x -> someBean.someFunctionThatReturnsMono(x) }
.retry(3)
.subscribeOn(Schedulers.elastic())
when:
StepVerifier.create(testMono).verifyError(IllegalArgumentException)
then:
4 * someBean.someFunctionThatReturnsMono(_ as X) >> Mono.error(new IllegalArgumentException())
}
Another one is to mock funtion to return PublisherProbe and count probe subscriptions:
def 'Test retry'() {
when:
PublisherProbe probe = PublisherProbe.of(Mono.error(new IllegalArgumentException()))
someBean = Mock(SomeBean) {
someFunctionThatReturnsMono(_) >> probe
}
def testMono = Mono.just(X)
.flatMap { x -> someBean.someFunctionThatReturnsMono(x) }
.retry(3)
.subscribeOn(Schedulers.elastic())
then:
StepVerifier.create(testMono).verifyError(IllegalArgumentException)
probe.subscribeCount() == 4
}
This is how I do it for one of my use-cases:
def "should retry failed action given number of times"() {
given:
def invocationCounter = new AtomicInteger(0)
when:
Mono<Void> mono = myService.doSth()
.doOnError { invocationCounter.incrementAndGet() }
then:
StepVerifier.create(mono)
.verifyErrorMatches { it == expectedError }
and:
invocationCounter.get() == 1 + retryNum
}
Related
I'm trying to write some unit tests for the following Kotlin code which uses async to create coroutines:
class ClassToTest(val dependency1: Dependency1, val dependency2: Dependency2) {
fun funToTest(list: List<X>) {
runBlocking {
val deferreds: List<Deferred<Type>> = list.map { item ->
async(Dispatchers.Default) {
val v1 = dependency1.func1(item)
val v2 = dependency2.func2(v1)
someFunc(v2)
}
}
val results = deferreds.awaitAll()
}
}
}
I mocked both dependency1 and dependency2 in the unit testing and provided a list of 2 items as input to the funToTest. I also mocked how the dependencies should return values based on different input, something like below
val slot1 = slot<>()
every {dependency1.func1(capture(slot1))} answers {
if (slot1.captured.field == xxx) {
return1
} else {
return2
}
}
//similar thing for dependency1
// invoke funToTest
However this doesn't seem like working as I'm getting unexpected results which indicated the mocked object didn't return results as desired between two coroutines.
Does anyone have any ideas what went wrong with these code?
I am trying to validate that a suspend function does not return anything at all in a certain test.
Consider the following situation:
val completionSignal = Channel<Unit>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
suspend fun waitForCompletionSignal(): String {
completionSignal.receive()
return "Completion signal received"
}
I want to test this code with 2 unit tests, one that validates it returns the string when I provide the CompletionSignal with a value (thats the easy one).
And one that validates that it does not return anything when i don't give it anything. This is the hard one, since how long should I wait? And can i be sure the test fails if my code changes and suddenly the string is returned?
I got the following approach but I am missing some pieces:
#Test
fun `waitForCompletionSignal when completionSignal is provided assert result`() = runTest {
// Start waiting for result
val result = async { waitForCompletionSignal() }
// Provide completion signal
completionSignal.trySend(Unit)
// Await result and verify its correct
assertThat(result.await() == "Completion signal received")
}
#Test
fun `waitForCompletionSignal when completionSignal is not provided assert no result`() = runTest {
// Start waiting for result
val result = async { waitForCompletionSignal() }
// TODO?? some validation that succeeds if the test is just like this, but fails when i do the following:
completionSignal.trySend(Unit)
// A regular await would wait indefinately, and checking if the deferred result is completed does not work very well as well.
}
I hope the question is clear, thanks in advance.
I made an extension function on the deferred type to be able to wait for a max amount of time and after that it will return null. In my particular situation a delay time of 0 (so no delay whatsoever) is enough, but I can imagine that in some situations its useful to delay for a minimum amount of time.
#ExperimentalCoroutinesApi
suspend inline fun <reified T> Deferred<T>.awaitOrNull(
time: Long = 0,
crossinline actBlock: () -> Unit = { }
): T? = coroutineScope {
actBlock()
val timeoutJob = async<T?> {
delay(time)
null
}
return#coroutineScope select<T?> {
this#awaitOrNull.onAwait {
timeoutJob.cancel()
it
}
timeoutJob.onAwait {
this#awaitOrNull.cancel()
it
}
}
}
Using this method i can write the following tests that fail / succeed as expected:
// Succeeds
#Test
fun `waitForCompletionSignal when completionSignal is provided assert result`() = runTest {
val result = async {
waitForCompletionSignal()
}.awaitOrNull {
completionSignal.trySend(Unit)
}
assertThat(result == "Completion signal received")
}
// Succeeds
#Test
fun `waitForCompletionSignal when completionSignal is not provided assert no result`() = runTest {
val result = async {
waitForCompletionSignal()
}.awaitOrNull()
assertThat(result == null)
}
// Fails (to prove that it works)
#Test
fun `waitForCompletionSignal when completionSignal is not provided assert no result`() = runTest {
val result = async {
waitForCompletionSignal()
}.awaitOrNull {
completionSignal.trySend(Unit) // This (obviously) causes the test to fail, as I wanted.
}
assertThat(result == null)
}
I'm trying to update a groovy unit test and I'm having an issue with the following code:
def "getDatasetRecords() returns PaginatedSearchResults with a set of DatasetRecords from the repo"() {
setup:
def mockRecords = []
def originalResults = Mock(PaginatedSearchResults)
def modelMock = Mock(Model) {
isEmpty() >> false
filter(_ as org.matonto.rdf.api.Resource, _ as IRI, _ as Value, null) >>> it // Also tried (*_), (_, _, _) w&w/o Classes
}
def recordMock = Mock(DatasetRecord)
recordMock.getModel() >> modelMock
7.times { mockRecords << recordMock }
originalResults.getPage() >> mockRecords
originalResults.getPageNumber() >> 1
originalResults.getTotalSize() >> 7
originalResults.getPageSize() >> 10
catalogManagerMock.findRecord(*_) >>> originalResults
expect:
def results = service.getDatasetRecords(new DatasetPaginatedSearchParams(vf))
results.getPage().size() == 7
results.getPageSize() == 10
results.getTotalSize() == 7
results.getPageNumber() == 1
}
When I debug the code: it appears that model.filter is returning null and a NPE is being thrown when isEmpty() is called here:
public Optional<DatasetRecord> getExisting(Resource resource, Model model, ValueFactory valueFactory, ValueConverterRegistry valueConverterRegistry) {
return (model.filter(resource, valueFactory.createIRI(RDF_TYPE_IRI), this.getTypeIRI()).isEmpty()?Optional.empty():Optional.of(new DatasetRecordImpl(resource, model, valueFactory, valueConverterRegistry)));
}
The NPE:
getDatasetRecords() returns PaginatedSearchResults with a set of DatasetRecords from the repo(org.matonto.dataset.impl.SimpleDatasetManagerSpec) Time elapsed: 0.028 sec <<< ERROR!
java.lang.NullPointerException
at org.matonto.dataset.ontology.dataset.DatasetRecordFactory.getExisting(DatasetRecordFactory.java:65)
at org.matonto.rdf.orm.AbstractOrmFactory.getExisting(AbstractOrmFactory.java:159)
at org.matonto.rdf.orm.AbstractOrmFactory.getExisting(AbstractOrmFactory.java:167)
at org.matonto.dataset.pagination.DatasetRecordSearchResults.lambda$new$0(DatasetRecordSearchResults.java:46)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at org.matonto.dataset.pagination.DatasetRecordSearchResults.<init>(DatasetRecordSearchResults.java:47)
at org.matonto.dataset.impl.SimpleDatasetManager.getDatasetRecords(SimpleDatasetManager.java:155)
at org.matonto.dataset.impl.SimpleDatasetManagerSpec.getDatasetRecords() returns PaginatedSearchResults with a set of DatasetRecords from the repo(SimpleDatasetManagerSpec.groovy:371)
The definition for filter:
Model filter(Resource subject, IRI predicate, Value object, Resource... context);
I have tried fully specking the method signature for the mock object with and without the last parameter. Any help would be appreciated getting the mock interceptor for the filter method working here.
Try this way:
class Spec extends Specification {
def 'mock returns itself'() {
given:
def mock = Mock(Model) {
filter(*_) >> it
}
expect:
mock.filter(1, 2, 3, 4) == mock
mock.filter(1, 2, 3) == mock
}
}
class Model {
Model filter(a, b, c, ... d) {
new Model()
}
}
My code (above) was fine, but a small change was made:
def modelMock = Mock(Model) {
isEmpty() >> false
filter(*_) >> it
}
My problem turned out to be IntelliJ and trying to debug it there.
In Mockito there is option to verify if mock method has been called, and specify timeout for this verification (VerificationWithTimeout), for example:
verify(mock, timeout(200).atLeastOnce()).baz();
It there any equivalent to such functionality in Spock?
I was trying to use PollingConditions to satisfy a similar scenario (which wasn't helpful), but instead found satisfaction in Spock's BlockingVariables. To verify that SomeService.method() is invoked at least once in function ClassBeingTested.method() within a given timeout period:
def "verify an interaction happened at least once within 200ms"(){
given:
def result = new BlockingVariable<Boolean>(0.2) // 200ms
SomeService someServiceMock = Mock()
someServiceMock.method() >> {
result.set(true)
}
ClassBeingTested clazz = new ClassBeingTested(someService: someServiceMock)
when:
clazz.someMethod()
then:
result.get()
}
When the result is set, the blocking condition will be satisfied and result.get() would have to return true for the condition to pass. If it fails to be set within 200ms, the test will fail with a timeout exception.
There is no equivalent in Spock. (PollingConditions can only be used for conditions, not for interactions.) The closest you can get is to add a sleep() statement in the then block:
when:
...
then:
sleep(200)
(1.._) * mock.baz()
Using PollingConditions and a boolean variable, the following example evaluates a function until it satisfies an interaction.
def "test the config watcher to reload games if a new customer directory is added"() {
given:
def conditions = new PollingConditions(initialDelay: 1, timeout: 60, factor: 1.25)
def mockGameConfigLoader = Mock(GameConfigLoader)
def gameConfigWatcher= new GameConfigWatcher(mockGameConfigLoader)
boolean gamesReloaded = false
when:
conditions.eventually {
gameConfigWatcher.listenEvents()
assert gamesReloaded
}
then:
1 * mockGameConfigLoader.loadAllGameConfigs() >> {
gamesReloaded = true
}
0 * _
}
This doesn't do exactly what the question asked, but I found it a bit cleaner that using a variable. If you have other conditions to asynchronously test in addition to the interaction, then you can declare the interactions at mock creation time and then use PollingConditions to test the other conditions. PollingConditions will either fail the test or block until the conditions pass, so that by the time the interaction is tested, the method should have been called:
#MicronautTest
class KernelManagerTest extends Specification {
#Inject
KernelManager kernelManager
//create conditions
PollingConditions conditions = new PollingConditions(timeout: 10, initialDelay: 1)
class Exits {
static createKernel (String[] args) {
System.exit(args[0].toInteger())
}
}
def "handles a system exit from within kernel"() {
given:
// set custom kernel
kernelManager.kernelClass = Exits
// create custom logger
kernelManager.log = Mock(Logger) {
1 * warn("Kernel exited unexpectedly.", _ as UnexpectedExitException)
}
when:
// create new kernel (exit 1)
kernelManager.startNewKernel("1")
then:
conditions.eventually {
kernelManager.kernelInstances.size() == 0
kernelManager.kernelThreads.size() == 0
}
}
}
I have the following functions in a controller
def render201 = {
render(status:201)
}
def render202 = {
response.setStatus(202)
}
def render203 = {
response.setStatus(203)
render(status:203)
}
def render204 = {
response.setStatus(204)
render(status:205)
}
And I have the following tests
void test201() {
controller.render201()
assertEquals(201, controller.response.status)
}
void test202() {
controller.render202()
assertEquals(202, controller.response.status)
}
void test203() {
controller.render203()
assertEquals(203, controller.response.status)
}
void test204() {
controller.render204()
assertEquals(204, controller.response.status)
}
test201 fails with this message
junit.framework.AssertionFailedError: expected:<201> but was:<200>
For some reason, if you don't explicitly set the response status, render will always return 200 when being run from a unit test.
Additionally, if I were to actually call these from a browser, render202 would return an error, but render201 and render203 would work just fine. I don't know what render204 would do.
What's going on here? Is this a bug in Grails?
Try something like this :
assertEquals(201, controller.renderArgs.status)
It worked for me.
If you want to understand the inside of mockController, look at :
https://svn.codehaus.org/grails/trunk/grails/src/groovy/grails/test/MockUtils.groovy
clazz.metaClass.getForwardArgs = {-> fwdArgs}
clazz.metaClass.getRedirectArgs ={-> redArgs}
clazz.metaClass.getRenderArgs ={-> renArgs}
clazz.metaClass.forward = {Map map -> forwardArgs.putAll(map)}
clazz.metaClass.redirect = {Map map -> redirectArgs.putAll(map)}
clazz.metaClass.render = {String text -> delegate.response.writer << text}
clazz.metaClass.render = {Converter arg -> delegate.response.writer << arg.toString()}
expected:<201> but was:<200> means you try to request operation which is returning some response. If you want to test 201 need to void method.