I want to inject Configuration instance in one of my testing classes, I extend my test class with ConfiguredApp and injected the Configuration, it looks like this:
#DoNotDiscover()
class MyApiServiceSpec extends FreeSpec with ScalaFutures with ConfiguredApp {
implicit val formats = DefaultFormats
implicit val exec = global
lazy val configuration = app.injector.instanceOf[Configuration]
"Global test" - {
"testcase 1" in {
Server.withRouter() {
case GET(p"/get/data") => Action { request =>
Results.Ok()
}
} { implicit port =>
WsTestClient.withClient { implicit client =>
val service = new MyApiService {
override def config: Configuration = configuration
override val ws: WSClient = client
}
whenReady(service.getData()) { res =>
//i will test stuff here
}
}
}
}
}
}
(MyApiService is a trait)
Exception encountered when invoking run on a nested suite -
ConfiguredApp needs an Application value associated with key
"org.scalatestplus.play.app" in the config map. Did you forget to
annotate a nested suite with #DoNotDiscover?
java.lang.IllegalArgumentException: ConfiguredApp needs an Application
value associated with key "org.scalatestplus.play.app" in the config
map. Did you forget to annotate a nested suite with #DoNotDiscover?
someone have an idea why is that...?
thanks!333333
My answer is not answer to current question, but I want give some advice. If you want to write unit tests for controllers or some service, I would suggest to use a PlaySpec. In order to inject custom configuration for testing environment:
class MyControllerSpec extends PlaySpec with OneAppPerSuite {
val myConfigFile = new File("app/test/conf/application_test.conf")
val parsedConfig = ConfigFactory.parseFile(myConfigFile)
val configuration = ConfigFactory.load(parsedConfig)
implicit override lazy val app: Application = new GuiceApplicationBuilder()
.overrides(bind[Configuration].toInstance(Configuration(configuration)))
.build()
"MyController #index" should {
"should be open" in {
val result = route(app, FakeRequest(GET, controllers.routes.MyController.index().url)).get
status(result) mustBe OK
}
}
}
It seems that you tried to run this test alone. But with a ConfiguredAppyou must run this test with a Suite, like
class AcceptanceSpecSuite extends PlaySpec with GuiceOneAppPerSuite {
override def nestedSuites = Vector(new MyApiServiceSpec)
}
The injection looks ok.
Related
I am having flink task and test spec like below. This testcase properly working as expected in Intellij. But, when running in Terminal mvn clean install, throws the org.apache.flink.streaming.runtime.tasks.StreamTaskException: Cannot load user class: org.mockito.codegen.mockHttpUtil
FlinkTask.scala
class FlinkTask(config: TaskConfig, httpUtil: HttpUtil) {
def process(): Unit = {
implicit val env: StreamExecutionEnvironment = FlinkUtil.getExecutionContext(config)
env.addSource(kafkaConnector.source(config.kafkaInputTopic)).name(config.eventConsumer)
.rebalance
.process(new TaskProcessFunction(config, httpUtil))
.name(config.taskProcessFunction)
.uid(config.taskProcessFunction)
.setParallelism(config.parallelism)
env.execute(config.jobName)
}
}
object FlinkTask {
def main(args: Array[String]): Unit = {
val config = new TaskConfig("config.path")
val httpUtil: HttpUtil = new HttpUtil
task = new FlinkTask(config, httpUtil)
task.process()
}
}
TaskProcessFunction.scala
class TaskProcessFunction(config: TaskConfig, httpUtil: HttpUtil) extends ProcessFunction[String, String] {
override def processElement(event: String, context: ProcessFunction[String, String]#Context, out: Collector[String]) {
...
}
}
HttpUtil.scala
class HttpUtil extends Serializable {
def get(url: String, headers: Map[String, String] = Map[String, String]("Content-Type"->"application/json")): HttpResponse = {
Unirest.get(url).headers(headers.asJava).asString() // returns HttpResponse
}
}
FlinkTaskTestSpec.scala
import org.mockito.Mockito
import org.scalatestplus.mockito.MockitoSugar
class FlinkTaskTestSpec extends BaseTestSpec with MockitoSugar {
val jobConfig = new TaskConfig("testconfig.path")
val mockHttpUtil = mock[HttpUtil](Mockito.withSettings().serializable())
"FlinkTask" should "generate event" in {
when(mockHttpUtil.get(contains("url"), any())).thenReturn(HttpResponse(200, "responsejson"))
new FlinkTask(jobConfig, mockHttpUtil).process()
}
}
Stacktrace
Cause: org.apache.flink.streaming.runtime.tasks.StreamTaskException: Cannot load user class: org.mockito.codegen.HttpUtil$MockitoMock$1840807158
ClassLoader info: URL ClassLoader:
Class not resolvable through given classloader.
at org.apache.flink.streaming.api.graph.StreamConfig.getStreamOperatorFactory(StreamConfig.java:322)
at org.apache.flink.streaming.runtime.tasks.OperatorChain.<init>(OperatorChain.java:146)
at org.apache.flink.streaming.runtime.tasks.StreamTask.beforeInvoke(StreamTask.java:485)
at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:531)
at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:722)
at org.apache.flink.runtime.taskmanager.Task.run(Task.java:547)
at java.base/java.lang.Thread.run(Thread.java:834)
...
Cause: java.lang.ClassNotFoundException: org.mockito.codegen.HttpUtil$MockitoMock$1840807158
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
Any help is appreciated
While it doesn't exactly provide the solution for the mock related problem, I really believie that You should avoid mocking whenever it's possible, especially in cases like that when You are going to serialize the mocked class.
You can easily avoid this by simply making FlinkTask take a trait that could have two implementations one for actual usage and the other for testing say TestHttpUtil that would basically be a stub.
I'm implementing a Vault client in Scala using Http4s client.
And I'm now starting to write integration tests. So far I have this:
abstract class Utils extends AsyncWordSpec with Matchers {
implicit override def executionContext = ExecutionContext.global
implicit val timer: Timer[IO] = IO.timer(executionContext)
implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)
val vaultUri = Uri.unsafeFromString(Properties.envOrElse("VAULT_ADDR", throw IllegalArgumentException))
val vaultToken = Properties.envOrElse("VAULT_TOKEN", throw IllegalArgumentException)
val clientResource = BlazeClientBuilder[IO](global)
.withCheckEndpointAuthentication(false)
.resource
def usingClient[T](f: VaultClient[IO] => IO[Assertion]): Future[Assertion] = {
clientResource.use { implicit client =>
f(new VaultClient[IO](vaultUri, vaultToken))
}.unsafeToFuture()
}
}
Then my tests look like this (just showing one test):
class SysSpec extends Utils {
"The auth endpoint" should {
"successfully mount an authentication method" in {
usingClient { client =>
for {
result <- client.sys.auth.create("test", AuthMethod(
"approle", "some nice description", config = TuneOptions(defaultLeaseTtl = 60.minutes)
))
} yield result should be (())
}
}
}
}
This approach works, however it doesn't feel right. For each test I'm opening the connection (clientResource.use) and recreating the VaultClient.
Is there a way for me to reuse the same connection and client for all the tests in SysSpec.
Please note these are integration tests and not unit tests.
This is the best I could come up with.
abstract class Utils extends AsyncWordSpec with Matchers with BeforeAndAfterAll {
implicit override def executionContext = ExecutionContext.global
implicit val timer: Timer[IO] = IO.timer(executionContext)
implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)
val (httpClient, finalizer) = BlazeClientBuilder[IO](global)
.withCheckEndpointAuthentication(false)
.resource.allocated.unsafeRunSync()
override protected def afterAll(): Unit = finalizer.unsafeRunSync()
private implicit val c = httpClient
val client = new VaultClient[IO](uri"http://[::1]:8200", "the root token fetched from somewhere")
}
Then the tests just use the client directly:
class SysSpec extends Utils {
"The auth endpoint" should {
"successfully mount an authentication method" in {
client.sys.auth.create("test", AuthMethod(
"approle", "some nice description",
config = TuneOptions(defaultLeaseTtl = 60.minutes))
).map(_ shouldBe ()).unsafeToFuture()
}
}
}
My two main problems with this approach are the two unsafeRunSyncs in the code. The first one is to create the client and the second one to clean the resource. However it is a much better approach then repeatedly creating and destroy the client.
I would also like not to use the unsafeToFuture but that would require ScalaTest to support Cats-Effect directly.
At the time of writing, Play 2.6 is in release candidate state.
The Action singleton has been deprecated, thus, all the information about testing here is deprecated:
https://www.playframework.com/documentation/2.6.0-RC2/ScalaTestingWebServiceClients
i.e. using the DSL for mock server routing like so:
Server.withRouter() {
case GET(p"/repositories") => Action {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
} { implicit port => ...
Causes deprecation warnings.
Is there a way to circumvent this or do we just need to wait for them to update their testing DSL?
Yes, there is a new way to do this with ScalaTest in Play framework 2.6. You need to use Guice to build an Application and inject your own RouterProvider. Consider this example:
class MyServiceSpec
extends PlaySpec
with GuiceOneServerPerTest {
private implicit val httpPort = new play.api.http.Port(port)
override def newAppForTest(testData: TestData): Application =
GuiceApplicationBuilder()
.in(Mode.Test)
.overrides(bind[Router].toProvider[RouterProvider])
.build()
def withWsClient[T](block: WSClient => T): T =
WsTestClient.withClient { client =>
block(client)
}
"MyService" must {
"do stuff with an external service" in {
withWsClient { client =>
// Create an instance of your client class and pass the WS client
val result = Await.result(client.getRepositories, 10.seconds)
result mustEqual List("octocat/Hello-World")
}
}
}
}
class RouterProvider #Inject()(action: DefaultActionBuilder) extends Provider[Router] {
override def get: Router = Router.from {
case GET(p"/repositories") => action {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
}
}
I have a service class in my project and I want to test one of its methods that is performing an api call, so I want to catch this call and return something fake so I can test my method, it looks like this:
class MyService #Inject()(implicit config: Configuration, wsClient: WSClient) {
def methodToTest(list: List[String]): Future[Either[BadRequestResponse, Unit]] = {
wsClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
response.status match {
case Status.OK =>
Right(logger.debug("Everything is OK!"))
case Status.BAD_REQUEST =>
Left(parse(response.body).extract[BadRequestResponse])
case _ =>
val ex = new RuntimeException(s"Failed with status: ${response.status} body: ${response.body}")
logger.error(s"Service failed: ", ex)
throw ex
}
}
}
}
and now in my test class I go:
class MyServiceTest extends FreeSpec with ShouldMatchers with OneAppPerSuite with ScalaFutures with WsScalaTestClient {
implicit lazy val materializer: Materializer = app.materializer
lazy val config: Configuration = app.injector.instanceOf[Configuration]
lazy val myService = app.injector.instanceOf[MyService]
"My Service Tests" - {
"Should behave as im expecting" in {
Server.withRouter() {
case POST(p"/fake/api/in/conf") => Action { request =>
Results.Ok
}
} { implicit port =>
WsTestClient.withClient { implicit client =>
whenReady(myService.methodToTest(List("1","2","3"))) { res =>
res.isRight shouldBe true
}
}
}
}
}
}
and I get this error:
scheme java.lang.NullPointerException: scheme
also tried put under client => :
val myService = new MyService {
implicit val config: Configuration = configuration
implicit val ws: WSClient = client
}
but got some other error that I dont have enough arguments in the constructor...
why is it not working?
if there is a better nd simpler way to fake this api call i will love to hear it :)
thanks!
Server.withRouter may not be exactly what you want. It creates a server and bound it to a random port, per instance (unless you specify the port). It also creates its own instance of application which will be disconnected from the app you used to instantiate the service.
Another thing is that the injected WSClient do not works relative to your application. You need to use the client which is passed to WsTestClient.withClient block instead. So, you should do something like:
class MyServiceTest extends FreeSpec with ShouldMatchers with OneAppPerSuite with ScalaFutures with WsScalaTestClient {
implicit lazy val materializer: Materializer = app.materializer
lazy val config: Configuration = app.injector.instanceOf[Configuration]
"My Service Tests" - {
"Should behave as im expecting" in {
Server.withRouter() {
case POST(p"/fake/api/in/conf") => Action { request =>
Results.Ok
}
} { implicit port =>
WsTestClient.withClient { implicit client =>
// Use the client "instrumented" by Play. It will
// handle the relative aspect of the url.
val myService = new MyService(client, config)
whenReady(myService.methodToTest(List("1","2","3"))) { res =>
res.isRight shouldBe true
}
}
}
}
}
}
I'm trying to run a test with scaldi and specs2. In the test I need to override a StringManipulator function that uses an injected ProxyManipulator. The ProxyManipulator takes a string and returns its upper case in a Future. The replacement manipulator in the test returns a Future("Test Message").
Here is the StringManipulator class where the injection occurs:
class StringManipulator {
def manip (str : String) (implicit inj: Injector) : String = {
val prox = inject[ProxyManipulator]
Await.result(prox.manipulate(str), 1 second)
}
}
I'm using a package.object that contains the implicit injector:
import modules.MyModule
package object controllers {
implicit val appModule = new MyModule
}
And here is the specs2 test with the new binding:
#RunWith(classOf[JUnitRunner])
class StringManipScaldiSpec extends Specification {
class TestModule extends Module {
bind [ProxyManipulator] to new ProxyManipulator {
override def manipulate(name: String) = Future("Test Message")
}
}
"Application" should {
"do something" in {
val myTestModule = new TestModule
val str = "my string"
val stringMan = new StringManipulator() //(myTestModule)
stringMan.manip(str)(myTestModule) === "Test Message"
}
}
}
The problem is that when the test runs the class StringManipulator is still using the original Proxy Manipulator instead of the one passed in the TestModule. Any ideas?