Skip to content

Commit

Permalink
[pekko-testkit-typed] Support for JUnit5 in testkit (#751)
Browse files Browse the repository at this point in the history
* JUnit5 support for testkit typed

* renamed TestKitJunitResource to TestKitJUnitResource

* renamed  "JUnit" to "Junit" to adhere to previous standard

* Cleanup remaining residuals of renaming from JUnit to Junit

* headers and scalafmt

* Change Junit5 to JUnit5

* revert @unused annotation

* scalafmt

* javafmt

* scalaify use of java optional

* add newline

* revert changes to Events.scala

* replace Option.when with if statement

---------

Co-authored-by: thomas <[email protected]>
  • Loading branch information
thmue and thomas authored Nov 10, 2023
1 parent d0e70eb commit 153b9c2
Show file tree
Hide file tree
Showing 11 changed files with 525 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.pekko.actor.testkit.typed.annotations;

import java.lang.annotation.*;

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JUnit5TestKit {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.pekko.actor.testkit.typed.javadsl

import org.apache.pekko
import com.typesafe.config.Config
import pekko.actor.testkit.typed.internal.TestKitUtils
import pekko.actor.testkit.typed.scaladsl.ActorTestKit.ApplicationTestConfig
import pekko.actor.typed.ActorSystem

final class JUnit5TestKitBuilder() {

var system: Option[ActorSystem[_]] = None

var customConfig: Config = ApplicationTestConfig

var name: String = TestKitUtils.testNameFromCallStack(classOf[JUnit5TestKitBuilder])

def withSystem(system: ActorSystem[_]): JUnit5TestKitBuilder = {
this.system = Some(system)
this
}

def withCustomConfig(customConfig: Config): JUnit5TestKitBuilder = {
this.customConfig = customConfig
this
}

def withName(name: String): JUnit5TestKitBuilder = {
this.name = name
this
}

def build(): ActorTestKit = {
if (system.isDefined) {
return ActorTestKit.create(system.get)
}
ActorTestKit.create(name, customConfig)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* license agreements; and to You under the Apache License, version 2.0:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* This file is part of the Apache Pekko project, which was derived from Akka.
*/

package org.apache.pekko.actor.testkit.typed.javadsl

import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation
import org.junit.jupiter.api.extension.{ ExtensionContext, InvocationInterceptor, ReflectiveInvocationContext }
import org.slf4j.LoggerFactory
import org.apache.pekko.actor.testkit.typed.internal.CapturingAppender

import java.lang.reflect.Method
import scala.util.control.NonFatal

final class LogCapturingExtension extends InvocationInterceptor {

private val capturingAppender = CapturingAppender.get("")

private val myLogger = LoggerFactory.getLogger(classOf[LogCapturing])

@throws[Throwable]
override def interceptTestMethod(invocation: Invocation[Void], invocationContext: ReflectiveInvocationContext[Method],
extensionContext: ExtensionContext): Unit = {

val testClassName = invocationContext.getTargetClass.getSimpleName
val testMethodName = invocationContext.getExecutable.getName

try {
myLogger.info(s"Logging started for test [${testClassName}: ${testMethodName}]")
invocation.proceed
myLogger.info(
s"Logging finished for test [${testClassName}: ${testMethodName}] that was successful")
} catch {
case NonFatal(e) =>
println(
s"--> [${Console.BLUE}${testClassName}: ${testMethodName}${Console.RESET}] " +
s"Start of log messages of test that failed with ${e.getMessage}")
capturingAppender.flush()
println(
s"<-- [${Console.BLUE}${testClassName}: ${testMethodName}${Console.RESET}] " +
s"End of log messages of test that failed with ${e.getMessage}")
throw e
} finally {

capturingAppender.clear()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.pekko.actor.testkit.typed.javadsl

import org.apache.pekko.actor.testkit.typed.annotations.JUnit5TestKit
import org.junit.jupiter.api.extension.{ AfterAllCallback, BeforeTestExecutionCallback, ExtensionContext }
import org.junit.platform.commons.support.AnnotationSupport

final class TestKitJUnit5Extension() extends AfterAllCallback with BeforeTestExecutionCallback {

var testKit: Option[ActorTestKit] = None

/**
* Get a reference to the field annotated with @JUnit5Testkit [[JUnit5TestKit]]
*/
override def beforeTestExecution(context: ExtensionContext): Unit = {
val testInstance: Option[AnyRef] =
if (context.getTestInstance.isPresent) Some(context.getTestInstance.get()) else None
testInstance.map(instance => {
val annotations = AnnotationSupport.findAnnotatedFieldValues(instance, classOf[JUnit5TestKit])
val fieldValue = annotations.stream().findFirst().orElseThrow(() =>
throw new IllegalArgumentException("Could not find field annotated with @JUnit5TestKit"))
testKit = Some(fieldValue.asInstanceOf[ActorTestKit])
})
}

/**
* Shutdown testKit
*/
override def afterAll(context: ExtensionContext): Unit = {
testKit.get.shutdownTestKit()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* license agreements; and to You under the Apache License, version 2.0:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* This file is part of the Apache Pekko project, which was derived from Akka.
*/

package jdocs.org.apache.pekko.actor.testkit.typed.javadsl;

import org.apache.pekko.actor.testkit.typed.annotations.JUnit5TestKit;
import org.apache.pekko.actor.Address;
import org.apache.pekko.actor.testkit.typed.javadsl.*;
import org.apache.pekko.actor.testkit.typed.javadsl.JUnit5TestKitBuilder;
import org.apache.pekko.actor.typed.ActorRef;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

// #junit5-integration
@DisplayName("JUnit5")
@ExtendWith(TestKitJUnit5Extension.class)
class JUnit5IntegrationExampleTest {

@JUnit5TestKit public ActorTestKit testKit = new JUnit5TestKitBuilder().build();

@Test
void junit5Test() {
Address address = testKit.system().address();
assertNotNull(address);
}

@Test
void testSomething() {

ActorRef<AsyncTestingExampleTest.Echo.Ping> pinger =
testKit.spawn(AsyncTestingExampleTest.Echo.create(), "ping");
TestProbe<AsyncTestingExampleTest.Echo.Pong> probe = testKit.createTestProbe();
pinger.tell(new AsyncTestingExampleTest.Echo.Ping("hello", probe.ref()));
AsyncTestingExampleTest.Echo.Pong pong =
probe.expectMessage(new AsyncTestingExampleTest.Echo.Pong("hello"));
assertEquals("hello", pong.message);
}

@Test
void testSomething2() {
ActorRef<AsyncTestingExampleTest.Echo.Ping> pinger2 =
testKit.spawn(AsyncTestingExampleTest.Echo.create(), "ping2");
TestProbe<AsyncTestingExampleTest.Echo.Pong> probe2 = testKit.createTestProbe();
pinger2.tell(new AsyncTestingExampleTest.Echo.Ping("hello", probe2.ref()));
AsyncTestingExampleTest.Echo.Pong pong =
probe2.expectMessage(new AsyncTestingExampleTest.Echo.Pong("hello"));
assertEquals("hello", pong.message);
}
}
// #junit5-integration
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package jdocs.org.apache.pekko.actor.testkit.typed.javadsl;

import org.apache.pekko.actor.testkit.typed.annotations.JUnit5TestKit;
import org.apache.pekko.actor.testkit.typed.javadsl.*;
import org.apache.pekko.actor.testkit.typed.javadsl.JUnit5TestKitBuilder;
import org.apache.pekko.actor.typed.ActorRef;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static jdocs.org.apache.pekko.actor.testkit.typed.javadsl.AsyncTestingExampleTest.Echo;

// test code copied from LogCapturingExampleTest.java

@DisplayName("JUnit5 log capturing")
@ExtendWith(TestKitJUnit5Extension.class)
@ExtendWith(LogCapturingExtension.class)
class LogCapturingExtensionExampleTest {

@JUnit5TestKit public ActorTestKit testKit = new JUnit5TestKitBuilder().build();

@Test
void testSomething() {
ActorRef<Echo.Ping> pinger = testKit.spawn(Echo.create(), "ping");
TestProbe<Echo.Pong> probe = testKit.createTestProbe();
pinger.tell(new Echo.Ping("hello", probe.ref()));
probe.expectMessage(new Echo.Pong("hello"));
}

@Test
void testSomething2() {
ActorRef<Echo.Ping> pinger = testKit.spawn(Echo.create(), "ping");
TestProbe<Echo.Pong> probe = testKit.createTestProbe();
pinger.tell(new Echo.Ping("hello", probe.ref()));
probe.expectMessage(new Echo.Pong("hello"));
}
}
// #log-capturing-junit5
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* license agreements; and to You under the Apache License, version 2.0:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* This file is part of the Apache Pekko project, which was derived from Akka.
*/

package org.apache.pekko.actor.testkit.typed.javadsl;

import org.apache.pekko.actor.testkit.typed.annotations.JUnit5TestKit;
import org.apache.pekko.Done;
import org.apache.pekko.actor.typed.javadsl.Behaviors;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.scalatestplus.junit.JUnitSuite;

import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static org.apache.pekko.Done.done;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@DisplayName("ActorTestKitTestJUnit5")
@ExtendWith(TestKitJUnit5Extension.class)
@ExtendWith(LogCapturingExtension.class)
class ActorTestKitJUnit5Test extends JUnitSuite {

@JUnit5TestKit public ActorTestKit testKit = new JUnit5TestKitBuilder().build();

@Test
void systemNameShouldComeFromTestClassViaJunitResource() {
assertEquals("ActorTestKitJUnit5Test", testKit.system().name());
}

@Test
void systemNameShouldComeFromTestClass() {
final ActorTestKit testKit2 = ActorTestKit.create();
try {
assertEquals("ActorTestKitJUnit5Test", testKit2.system().name());
} finally {
testKit2.shutdownTestKit();
}
}

@Test
void systemNameShouldComeFromGivenClassName() {
final ActorTestKit testKit2 = ActorTestKit.create(HashMap.class.getName());
try {
// removing package name and such
assertEquals("HashMap", testKit2.system().name());
} finally {
testKit2.shutdownTestKit();
}
}

@Test
void testKitShouldSpawnActor() throws Exception {
final CompletableFuture<Done> started = new CompletableFuture<>();
testKit.spawn(
Behaviors.setup(
(context) -> {
started.complete(done());
return Behaviors.same();
}));
assertNotNull(started.get(3, TimeUnit.SECONDS));
}
}
3 changes: 3 additions & 0 deletions actor-testkit-typed/src/test/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@

# used by ActorTestKitSpec
test.from-application = yes

# used by JUnit5TestKitBuilderSpec
test.value = someValue
Loading

0 comments on commit 153b9c2

Please sign in to comment.