I am trying to use spray route and want to test it with Spray-TestKit.
I am using :
- Scala 2.10.3
- Akka 2.3.3
- Spray 1.3.1
I create a trait extending HttpService, where I define a route :
trait MyService extends HttpService with CoreAccess {
import greentee.am.endpoint.tsmsp.tsmSPJsonSupport._
val myRoute = {
path("resources"/"ping") {
get {
complete(OK, "pong")
}
}
}
}
I deleted part of the route which was not relevant.
CoreAccess is a trait extending Actor, because I have methods in that trait access the ActorSystem. (I don't know who to retrieve ActorSelection from a trait without it extending an actor)
Then I create a test Specification
import MyService
import org.specs2.mutable.Specification
import spray.testkit.Specs2RouteTest
import spray.http.StatusCodes._
class RegistrationRouteSpecification extends Specification with Specs2RouteTest with MyService {
def actorRefFactory = system
"The EndPoint " should {
"return Pong to a Get request to the ping" in {
Get("/resources/ping") ~> myRoute ~> check {
status === OK
responseAs[String] === "pong"
}
}
}
}
When I try to execute the test, I get the following compilation error:
[info] Compiling 1 Scala source to /Users/IdeaProjects/endpoint/target/scala-2.10/test-classes...
[error] /Users/IdeaProjects/endpoint/src/test/scala/RegistrationRouteSpecification.scala:19: could not find implicit value for parameter ta: RegistrationRouteSpecification.this.TildeArrow[spray.routing.RequestContext,Unit]
[error] Get("/resources/ping") ~> myRoute ~> check {
[error] ^
[error] one error found
I answer my own question.
I corrected my Build.scala to use the following lines:
val scalaCheck = "org.scalacheck" %% "scalacheck" % Versions.scalaCheckVersion % "test"
val scalaTest = "org.scalatest" %% "scalatest" % "2.2.0" % "test"
Instead of using a simple '%' and supplying a dedicated version.
Related
I want to add integration tests to my project using cucumber feature files. I have got this working using this project as an example: https://github.com/jecklgamis/cucumber-jvm-scala-example
The problem I am running into is when I want to mock some objects. ScalaMock and EasyMock all seem to need scalatest or something similar.
My build.sbt has these lines:
libraryDependencies ++= Seq(
"io.cucumber" %% "cucumber-scala" % "2.0.1" % Test,
"io.cucumber" % "cucumber-junit" % "2.0.1" % Test,
"org.scalamock" %% "scalamock" % "4.0.0" % Test,
"org.scalatest" %% "scalatest" % "3.0.1" % Test,
etc..
My stepdef file has this:
import com.typesafe.config.{Config, ConfigFactory}
import cucumber.api.scala.{EN, ScalaDsl}
import eu.xeli.jpigpio.JPigpio
class StepDefs extends ScalaDsl with EN {
var config: Config = null
var jpigpio: JPigpio = null
Given("""^an instance of pigpio$""") { () =>
jpigpio = mock[JPigpio]
}
}
The mock[JPigpio] call gives a symbol not found error. I assume because this class does not extend MockFactory.
How can I use scalamock outside of an MockFactory class?
Bit of a quick and dirty example that does not pull in Scalatest, but I'm sure you can piece the rest together. I'd actually be curious to see this working with Cucumber :)
import org.scalamock.MockFactoryBase
import org.scalamock.clazz.Mock
object NoScalaTestExample extends Mock {
trait Cat {
def meow(): Unit
def isHungry: Boolean
}
class MyMockFactoryBase extends MockFactoryBase {
override type ExpectationException = Exception
override protected def newExpectationException(message: String, methodName: Option[Symbol]): Exception =
throw new Exception(s"$message, $methodName")
def verifyAll(): Unit = withExpectations(() => ())
}
implicit var mc: MyMockFactoryBase = _
var cat: Cat = _
def main(args: Array[String]): Unit = {
// given: I have a mock context
mc = new MyMockFactoryBase
// and am mocking a cat
cat = mc.mock[Cat]
// and the cat meows
cat.meow _ expects() once()
// and the cat is always hungry
cat.isHungry _ expects() returning true anyNumberOfTimes()
// then the cat needs feeding
assert(cat.isHungry)
// and the mock verifies
mc.verifyAll()
}
}
This will actually throw as the meows expectation is not satisfied (just to demo)
Exception in thread "main" java.lang.Exception: Unsatisfied expectation:
Expected:
inAnyOrder {
<mock-1> Cat.meow() once (never called - UNSATISFIED)
<mock-1> Cat.isHungry() any number of times (called once)
}
Actual:
<mock-1> Cat.isHungry(), None
at NoScalaTestExample$MyMockFactoryBase.newExpectationException(NoScalaTestExample.scala:13)
at NoScalaTestExample$MyMockFactoryBase.newExpectationException(NoScalaTestExample.scala:10)
at org.scalamock.context.MockContext$class.reportUnsatisfiedExpectation(MockContext.scala:45)
at NoScalaTestExample$MyMockFactoryBase.reportUnsatisfiedExpectation(NoScalaTestExample.scala:10)
I am trying to run some functional tests with play2-reactivemongo. I will try to be as concrete as possible, but if something is missing please let me know.
My dependencies are here
libraryDependencies ++= Seq(
cache,
"org.reactivemongo" %% "play2-reactivemongo" % "0.12.0",
"com.mohiva" %% "play-silhouette" % "4.0.0",
"com.mohiva" %% "play-silhouette-testkit" % "4.0.0" % "test",
specs2 % Test
)
In MongoUserDao.scala
import play.modules.reactivemongo._
import play.modules.reactivemongo.json._
import reactivemongo.play.json.collection.JSONCollection
class MongoUserDao #Inject() (val reactiveMongoApi: ReactiveMongoApi) extends UserDao {
val usersFuture = reactiveMongoApi.database.map(_.collection[JSONCollection]("users"))
...
}
In DaoSpecResources.scala
trait DaoSpecResources {
val timeout = DurationInt(10).seconds
val fakeApp = new GuiceApplicationBuilder()
.in(Mode.Test)
.configure(
"play.modules.enabled" -> List("play.modules.reactivemongo.ReactiveMongoModule"),
"mongodb.uri" -> "mongodb://localhost:27017/test"
)
.build
val reactiveMongoApi = fakeApp.injector.instanceOf[ReactiveMongoApi]
...
}
When I try to run the test I get this error
[error] cannot create an instance for class daos.UserDaoSpec
[error] caused by com.google.inject.CreationException: Unable to create injector, see the following errors:
[error]
[error] 1) No implementation for play.api.inject.ApplicationLifecycle was bound.
[error] while locating play.api.inject.ApplicationLifecycle
[error] for parameter 1 at services.ApplicationTimer.<init>(ApplicationTimer.scala:24)
[error] at Module.configure(Module.scala:23) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
[error]
[error] 1 error
This is caused by app/services/ApplicationTimer.scala, which depends on ApplicationLifecycle, but you haven't bound any implementation to ApplicationLifecycle. ApplicationTimer is a demo included in every new Play project. You should probably remove it completely if you don't need it, otherwise at least disable it if running in test mode. See disabling modules and overriding modules.
Solution
However, since DefaultReactiveMongoApi also depends on ApplicationLifecycle, you'll need to provide a binding to an implementation of ApplicationLifecycle. The easiest way:
import play.api.inject.{ ApplicationLifecycle, DefaultApplicationLifecycle }
import play.api.inject.bind
trait DaoSpecResources {
val timeout = DurationInt(10).seconds
val fakeApp = new GuiceApplicationBuilder()
.in(Mode.Test)
.configure(
"play.modules.enabled" -> List("play.modules.reactivemongo.ReactiveMongoModule"),
"mongodb.uri" -> "mongodb://localhost:27017/test"
)
.bindings(bind[ApplicationLifecycle].to[DefaultApplicationLifecycle])
.build
val reactiveMongoApi = fakeApp.injector.instanceOf[ReactiveMongoApi]
val lifecycle = fakeApp.injector.instanceOf[DefaultApplicationLifecycle]
def stopApp = lifecycle.stop()
}
(added 5 lines: imports, bindings, lifecycle and stopApp)
Then, in your test spec, add step(stopApp) at the end, like so:
class FooSpec extends PlaySpecification with DaoSpecResources {
// Your examples...
step(stopApp)
}
Alternative solution
Personally, I'd create a specialized trait extending Specification or PlaySpecification which would set up and tear down everything automatically, like in this example from specs2 documentation.
trait PlayWithMongoSpecification extends PlaySpecification {
val timeout = DurationInt(10).seconds
val fakeApp = new GuiceApplicationBuilder()
.in(Mode.Test)
.configure(
"play.modules.enabled" -> List("play.modules.reactivemongo.ReactiveMongoModule"),
"mongodb.uri" -> "mongodb://localhost:27017/test"
)
.bindings(bind[ApplicationLifecycle].to[DefaultApplicationLifecycle])
.build
val reactiveMongoApi = fakeApp.injector.instanceOf[ReactiveMongoApi]
val lifecycle = fakeApp.injector.instanceOf[DefaultApplicationLifecycle]
def stopApp = lifecycle.stop()
override def map(fs: =>Fragments) = fs ^ step(stopApp)
}
class FooSpec extends PlayWithMongoSpecification {
// Your examples...
}
You may consider making reactiveMongoApi a lazy val.
I am trying to write an Akka HTTP microservice (akka version 2.4.11, Scala version 2.11.8, both latest versions at time of writing) which is aware of the client service's IP (i.e., remote address), and I cannot get this to work.
I can create and run a service which says 'Hello!' using a route like this:
val routeHello: Route = path("SayHello") {
get {
entity(as[String]) {
body => complete {
HttpResponse(entity = HttpEntity("Hello!"))
}
}
}
}
I have constructed a similar route to the one above, which is extended so that it is aware of the client's IP address.
I noted that I need to edit the application.conf file and set 'remote-address-header = on' to enable the addition of a Remote-Address header holding the clients (remote) IP address. I have done this in case it is required.
Here is the route:
val routeHelloIp: Route = path("SayHelloIp") {
get {
// extractClientIp appears to be working as a filter
// instead of an extractor - why?
extractClientIp {
clientIp => {
entity(as[String]) {
body => complete {
HttpResponse(entity = HttpEntity("Hello!"))
}
}
}
}
}
}
However when I run this route, I get a message 'The requested resource could not be found.'.
It looks like I have got the Akka-http DSL syntactic sugar wrong in the example above. I would be grateful if you could put me on the right path!
EDIT:
I have tried the following program in response to Ramon's helpful answer. Unfortunately it does not compile and I cannot see what I need to do to make it compile.
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.IncomingConnection
import java.net.InetSocketAddress
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import akka.http.scaladsl.server.Directives._
import java.net.InetSocketAddress
object TestHttp {
def main(args: Array[String]) {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
// allow connections from any IP
val interface = "0.0.0.0"
//from the question
def createRoute(address: InetSocketAddress) = path("SayHelloIp") {
get {
extractRequestEntity { entity =>
entity(as[String]) { body =>
complete(entity = s"Hello ${address.getAddress().getHostAddress()}")
}
}
}
}
Http().bind(interface).runWith(Sink foreach { conn =>
val address = conn.remoteAddress
conn.handleWithAsyncHandler(createRoute(address))
})
}
}
I have the following build.sbt to ensure that the latest version of Scala and akka-http are used:
import sbt.Keys._
name := "Find my IP"
version := "1.0"
scalaVersion := "2.11.8"
resolvers ++= Seq(
"Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
)
libraryDependencies ++= {
Seq(
"com.typesafe.akka" %% "akka-actor" % "2.4.11",
"com.typesafe.akka" %% "akka-stream" % "2.4.11",
"com.typesafe.akka" %% "akka-http-experimental" % "2.4.11",
"com.typesafe.akka" %% "akka-http-core" % "2.4.11"
)
}
I get the following compile-time errors:
[error] /Users/tilopa/temp/akka-test/src/main/scala/Test.scala:24: akka.http.scaladsl.model.RequestEntity does not take parameters
[error] entity(as[String]) { body =>
[error] ^
[error] /Users/tilopa/temp/akka-test/src/main/scala/Test.scala:25: reassignment to val
[error] complete(entity = s"Hello ${address.getAddress().getHostAddress()}")
[error] ^
[error] two errors found
[error] (compile:compileIncremental) Compilation failed
Using extractClientIp
extractClientIp is not working for you because the sender has not specified one of the required header fields. From the documentation:
Provides the value of X-Forwarded-For, Remote-Address, or X-Real-IP
headers as an instance of RemoteAddress.
You just have to turn on the right setting in your sender:
The akka-http server engine adds the Remote-Address header to every
request automatically if the respective setting
akka.http.server.remote-address-header is set to on. Per default it is
set to off.
generic solution
If you want this to work for any HttpRequest, not just the ones with the correct header settings, then you have to use the bind method on an HttpExt instead of bindAndHandle:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.IncomingConnection
import java.net.InetSocketAddress
implicit val actorSystem : ActorSystem = ???
implicit val actorMat = ActorMaterializer()
//alow connections from any IP
val interface = "0.0.0.0"
//from the question
def createRoute(address : InetSocketAddress) = path("SayHelloIp") {
get {
extractRequestEntity { entity =>
entity(as[String]) { body =>
complete(entity = s"Hello ${address.getAddress().getHostAddress()}")
}
}
}
}
Http().bind(interface).runWith(Sink foreach { conn =>
val address = conn.remoteAddress
conn.handleWithAsyncHandler(createRoute(address))
})
Edit
As noted in the comments: since akka 10.0.13 use conn.handleWith.
HttpExt.bind() has been depreciated. Here's a workaround with connectionSource():
val https: HttpsConnectionContext = /*...*/
Http(actorSystem).
newServerAt(interface,httpsPort).
enableHttps(https).
connectionSource().
to(Sink.foreach { connection =>
println("New Connection from remote address :: ${connection.remoteAddress}")
// handle connection here
}).run().onComplete {
case Success(binding)=>
binding.addToCoordinatedShutdown(hardTerminationDeadline = 10.seconds)
log.info("{} HTTPS Server running {}",errorContext,binding)
case Failure(ex)=>
log.error("{} HTTPS Server failed {}",errorContext,ex.toString)
sys.exit(10)
}
Play 2.4 app, using dependency injection for service classes.
I found that Specs2 chokes when a service class being tested has more than one injected dependency. It fails with "Can't find a constructor for class ..."
$ test-only services.ReportServiceSpec
[error] Can't find a constructor for class services.ReportService
[error] Error: Total 1, Failed 0, Errors 1, Passed 0
[error] Error during tests:
[error] services.ReportServiceSpec
[error] (test:testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 2 s, completed Dec 8, 2015 5:24:34 PM
Production code, stripped to bare minimum to reproduce this problem:
package services
import javax.inject.Inject
class ReportService #Inject()(userService: UserService, supportService: SupportService) {
// ...
}
class UserService {
// ...
}
class SupportService {
// ...
}
Test code:
package services
import javax.inject.Inject
import org.specs2.mutable.Specification
class ReportServiceSpec #Inject()(service: ReportService) extends Specification {
"ReportService" should {
"Work" in {
1 mustEqual 1
}
}
}
If I remove either UserService or SupportService dependency from ReportService, the test works. But obviously the dependencies are in the production code for a reason. Question is, how do I make this test work?
Edit: When trying to run the test inside IntelliJ IDEA, the same thing fails, but with different messages: "Test framework quit unexpectedly", "This looks like a specs2 exception..."; see full output with stacktrace. I opened a Specs2 issue as instructed in the output, though I have no idea if the problem is in Play or Specs2 or somewhere else.
My library dependencies below. (I tried specifying Specs2 version explicitly, but that didn't help. Looks like I need specs2 % Test as is, for Play's test classes like WithApplication to work.)
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
libraryDependencies ++= Seq(
specs2 % Test,
jdbc,
evolutions,
filters,
"com.typesafe.play" %% "anorm" % "2.4.0",
"org.postgresql" % "postgresql" % "9.4-1205-jdbc42"
)
There is limited support for dependency injection in specs2, mostly for execution environments or command-line arguments.
There is nothing preventing you from just using a lazy val and your favourite injection framework:
class MySpec extends Specification with Inject {
lazy val reportService = inject[ReportService]
...
}
With Play and Guice, you could have a test helper such as this:
import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag
trait Inject {
lazy val injector = (new GuiceApplicationBuilder).injector()
def inject[T : ClassTag]: T = injector.instanceOf[T]
}
If you really need runtime dependency injection, then it's better to use Guice loading, I guess:
package services
import org.specs2.mutable.Specification
import scala.reflect.ClassTag
import com.google.inject.Guice
// Something you'd like to share between your tests
// or maybe not
object Inject {
lazy val injector = Guice.createInjector()
def apply[T <: AnyRef](implicit m: ClassTag[T]): T =
injector.getInstance(m.runtimeClass).asInstanceOf[T]
}
class ReportServiceSpec extends Specification {
lazy val reportService: ReportService = Inject[ReportService]
"ReportService" should {
"Work" in {
reportService.foo mustEqual 2
}
}
}
Alternatively you can implement Inject object as
import scala.reflect.ClassTag
import play.api.inject.guice.GuiceApplicationBuilder
object Inject {
lazy val injector = (new GuiceApplicationBuilder).injector()
def apply[T : ClassTag]: T = injector.instanceOf[T]
}
It depends whether you want to use Guice directly, or thru play wrappers.
Looks like you are out of luck ATM: The comment says
Try to create an instance of a given class by using whatever constructor is available and trying to instantiate the first parameter recursively if there is a parameter for that constructor.
val constructors = klass.getDeclaredConstructors.toList.filter(_.getParameterTypes.size <= 1).sortBy(_.getParameterTypes.size)
i.e. Specs2 doesn't provide own DI out-of-the box,
Or you can reimplement the functionality yourself, if Guice isn't working for you.
App code:
package services
import javax.inject.Inject
class ReportService #Inject()(userService: UserService, supportService: SupportService) {
val foo: Int = userService.foo + supportService.foo
}
class UserService {
val foo: Int = 1
}
class SupportService {
val foo: Int = 41
}
Test code
package services
import org.specs2.mutable.Specification
import scala.reflect.ClassTag
import java.lang.reflect.Constructor
class Trick {
val m: ClassTag[ReportService] = implicitly
val classLoader: ClassLoader = m.runtimeClass.getClassLoader
val trick: ReportService = Trick.createInstance[ReportService](m.runtimeClass, classLoader)
}
object Trick {
def createInstance[T <: AnyRef](klass: Class[_], loader: ClassLoader)(implicit m: ClassTag[T]): T = {
val constructors = klass.getDeclaredConstructors.toList.sortBy(_.getParameterTypes.size)
val constructor = constructors.head
createInstanceForConstructor(klass, constructor, loader)
}
private def createInstanceForConstructor[T <: AnyRef : ClassTag]
(c: Class[_], constructor: Constructor[_], loader: ClassLoader): T = {
constructor.setAccessible(true)
// This can be implemented generically, but I don't remember how to deal with variadic functions
// generically. IIRC even more reflection.
if (constructor.getParameterTypes.isEmpty)
constructor.newInstance().asInstanceOf[T]
else if (constructor.getParameterTypes.size == 1) {
// not implemented
null.asInstanceOf[T]
} else if (constructor.getParameterTypes.size == 2) {
val types = constructor.getParameterTypes.toSeq
val param1 = createInstance(types(0), loader)
val param2 = createInstance(types(1), loader)
constructor.newInstance(param1, param2).asInstanceOf[T]
} else {
// not implemented
null.asInstanceOf[T]
}
}
}
// NB: no need to #Inject here. The specs2 framework does it for us.
// It sees spec with parameter, and loads it for us.
class ReportServiceSpec (trick: Trick) extends Specification {
"ReportService" should {
"Work" in {
trick.trick.foo mustEqual 2
}
}
}
And that expectedly fails with
[info] ReportService should
[error] x Work
[error] '42' is not equal to '2' (FooSpec.scala:46)
If you don't need runtime dependency injection, then it's better to use cake pattern, and forget reflection all-together.
My colleague suggested a "low-tech" workaround. In the test, instantiate service classes with new:
class ReportServiceSpec extends Specification {
val service = new ReportService(new UserService, new SupportService)
// ...
}
This also works:
class ReportServiceSpec #Inject()(userService: UserService) extends Specification {
val service = new ReportService(userService, new SupportService)
// ...
}
Feel free to post more elegant solutions. I've yet to see a simple DI solution that works (with Guice, Play's default).
Does anyone else find it curious that Play's default test framework does not play well with Play's default DI mechanism?
Edit: In the end I went with an "Injector" test helper, almost the same as what Eric suggested:
Injector:
package testhelpers
import play.api.inject.guice.GuiceApplicationBuilder
import scala.reflect.ClassTag
/**
* Provides dependency injection for test classes.
*/
object Injector {
lazy val injector = (new GuiceApplicationBuilder).injector()
def inject[T: ClassTag]: T = injector.instanceOf[T]
}
Test:
class ReportServiceSpec extends Specification {
val service = Injector.inject[ReportService]
// ...
}
I'm new to Scala, and trying to write a little REST API. I am using Scala 11.2, Spray 1.3.1 and akka 2.3.6.
I am basically trying to compile an example from spray.
The error I get for each of the routes is:
type mismatch; found : String("pong!!!!!!!!") required: spray.httpx.marshalling.ToResponseMarshallable
I am unsure if it is a versions incompatibility issue or I am missing a reference.
Here is my route definition taken from the spray example :
package com.Shenandoah.SBIR.httpInterface
import spray.routing.HttpService
trait HttpInterface extends HttpService {
def pingRoute = path("ping") {
get { complete("pong!!!!!!!!") }
}
def pongRoute = path("pong") {
get { complete("pong!?") }
}
def pipRoute = path("pip") {
get { complete("moonshine") }
}
def rootRoute = pingRoute ~ pongRoute ~ pipRoute
}
Here is the actor:
package com.Shenandoah.SBIR.httpInterface
import akka.actor._
class HttpInterfaceActor extends HttpInterface with Actor {
// the HttpService trait defines
// only one abstract member, which connects the services environment
// to the enclosing actor or test.
def actorRefFactory = context
def receive = runRoute(rootRoute)
}
You are probably using the dependency "io.spray" % "spray-routing" % "2.3.6" which is for Scala 2.10. There is a Spray version published without the Scala version designation, which is compiled against Scala 2.10. This is unfortunate.
Use "io.spray" %% "spray-routing" % "2.3.6" (note the double %) to pull in the dependency matching your Scala version. This will work with both Scala 2.10 and 2.11.
Looks like you are missing the default marshallers. Try adding to your imports:
import spray.httpx.marshalling.Marshaller