Trying to do some TDD while learning the playframework:
class ContentFetching extends PlaySpec with BeforeAndAfter with MockFactory
{
private val service = ContentService
private val languages = List(Language(Some(1), "en", "English", "Placeholder"),
Language(Some(2), "de", "Deutsch", "Platzhalter")
)
"find" must
{
"fail if languageCode is invalid" in
{
val fakeRepository = mock[LanguageRepositoryTrait]
(fakeRepository.get _).expects().returning(languages)
fakeRepository.get must have length 3
service.find("fr") must be Result.NotFound
}
}
}
ContentService would call:
def fourOhFour() = NotFound(s"Oops, content could not be found")
yet the assertion service.find("fr") must be Result.NotFound won't compile. Why is that and how to test this then?
Here is the whole contentService (as requested) Sadly it extends Controller currently, because I found no other way to return an Action. Actually I use MVC, but a Service + repository-layer as well:
class ContentServiceComponent(languageRepository: LanguageRepositoryTrait, nodeRepository: NodeRepositoryTrait) extends Controller
{
def find(language: String) = Action
{
languageRepository.get().map(l => l.code).contains(language) match
{
case true => Ok(s"Homepage for $language")
case false => fourOhFour()
}
}
def fourOhFour() = NotFound(s"Oops, content could not be found")
}
object ContentService extends ContentServiceComponent(LanguageRepository, NodeRepository)
service.find("fr") from the Play controller returns a Result
Maybe you're comparing a Result with Result.Status type?
Can you use a play.api.helper?
status(of: Future[Result]) = Await.result(of, timeout.duration).header.status
Instead of
service.find("fr") must be Result.NotFound
Try something like this?
status(service.find("fr")) must be NOT_FOUND
NOT_FOUND is a play.api.http.Status value and just maps to the standard HTTP status code 404
find instead returns Action could return Option
def find(language: String): Option[String] =
{
languageRepository.get().map(l => l.code).contains(language) match
{
case true => Some(s"Homepage for $language")
case _ => None
}
}
So you cound handle it at Controller as follow:
def index(language:String) = Action{
ContentService.find(language) match{
case Some(content) => Ok(content)
case _ => NotFound(s"Oops, content could not be found")
}
}
and you could do test case something like this:
class ContentFetching extends PlaySpec with BeforeAndAfter with MockFactory
{
val languages = List(Language(Some(1), "en", "English",Placeholder"),Language(Some(2), "de", "Deutsch", "Platzhalter")
)
val languagesRepositoryMockHere = new LanguageRepositoryTrait{
override def get = languages //I don't know the implementations
}
"find" must
{
"fail if languageCode is invalid" in
{
private val service = new ContentServiceComponent(languagesRepositoryMockHere,nodeRepositoryMockHere)
service.find("fr") shouldBe None
}
}
}
This was not tested but could help for reference.
Cheers.
Related
I am trying to implement a test with ScalaTest (WordSpecLike, MustMatchers) that verify if an Either[Seq[Error], Seq[Value]] contains one value and if the value has some specific attributes.
Ideally I would like to have something like:
val result:Either[Seq[Error], Seq[Value]] = ...
result.value must contain { _ have (
'prop ("value")
)}
But this does not compile and I have no clue on how to achieve this kind of matching.
Is there documentation on deep tests or some best practices for that?
Inspectors enable assertions to be made about collections, and in combination with EitherValues, as suggested by #LuisMiguelMejíaSuárez, and checking arbitrary properties with have, the following syntax is possible
atLeast(1, result.right.value) must have ('prop ("value"))
Here is a working example
class Value(val foo: String, val bar: Int) {
def isTooLong: Boolean = foo.length > 2
}
class StackOverflowSpec extends WordSpec with MustMatchers with EitherValues with Inspectors {
"Result" when {
"Right" should {
"have property" in {
val result: Either[Seq[Error], Seq[Value]] = Right(Seq(new Value("aa", 11), new Value("bbb", 42)))
atLeast(1, result.right.value) must have ('tooLong (true), 'bar (42) )
}
}
}
}
Alternatively try pattern matching on result and passing the property predicate to Seq.exists like so
class Value(val foo: String, val bar: String) {
def isTooLong: Boolean = foo.length > 2
}
class StackOverflowSpec extends WordSpec with MustMatchers {
"Result" when {
"Right" should {
"have property" in {
val result: Either[Seq[Error], Seq[Value]] = Right(Seq(new Value("a", "b"), new Value("ccc", "ddd")))
result match {
case Left(seq) => fail
case Right(seq) => seq.exists(_.isTooLong) must be(true)
}
}
}
}
}
I'm working through a slight variation of Sangria's Getting Started, using Akka HTTP. I'm attempting to use json4s-jackson as the serializaltion lib, but am running in to some trouble getting the response I want.
Specifically, the serialized response I get is the JSON version of the (StatusCode, Node) tuple:
{
"_1": {
"allowsEntity": true,
"defaultMessage": "OK",
"intValue": 200,
"reason": "OK"
},
"_2": {
"data": {
"foo": {
"id": "1",
"name": "Foo"
}
}
}
}
The data portion is correct, but obviously I just want that and not the first element of the serialized tuple.
I'm using akka-http-json4s, so my trait with route looks like:
case class GraphQlData(query: String, operation: Option[String])
trait FooController {
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
val fooRoutes = post {
entity(as[GraphQlData]) { data =>
QueryParser.parse(data.query) match {
// query parsed successfully, time to execute it!
case Success(queryAst) =>
complete {
Executor
.execute(
SchemaDefinition.FooSchema,
queryAst,
new FooService,
operationName = data.operation
)
.map(OK -> _)
.recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
}
// can't parse GraphQL query, return error
case Failure(error) =>
complete(BadRequest -> error.getMessage)
}
}
}
implicit def executionContext: ExecutionContext
}
For the life of me I can't figure out what's wrong. I've been looking at sangria-akka-http-example but it seems to be exactly the same, with the exception of using spray-json instead of json4s.
Ideas? Thanks!
Ah, figured it out. I neglected to add
import sangria.marshalling.json4s.jackson._
to the trait defining the route. Adding it does the trick.
Just wanted to provide a quick update to this answer for the full GraphQLRequest. Now the variables are included in the request.
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s._
import org.json4s.JsonAST.JObject
import sangria.marshalling.json4s.jackson._
case class GQLRequest(query: String, operationName: Option[String], variables: JObject)
trait SomeJsonSupport extends Json4sSupport {
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
}
trait GraphQLResource extends SomeJsonSupport{
implicit val timeout:Timeout
implicit val system:ActorSystem
import system.dispatcher
def graphqlRoute: Route =
(post & path("graphql")) {
entity(as[GQLRequest]) { requestJson =>
println(s"This is the requestJson = $requestJson")
graphQLEndpoint(requestJson)
}
} ~
get {
println(s"This is working")
getFromResource("graphiql.html")
}
def graphQLEndpoint(requestJson: GQLRequest): Route = {
val route = QueryParser.parse(requestJson.query) match {
case Success(query) =>
println(s"This is the query $query")
val vars = requestJson.variables match {
case jObj:JObject => jObj
case _ => JObject(List.empty)
}
val futureJValue = Executor.execute(clientSchema,
query,
NclhGqlRequest(this),
operationName = requestJson.operationName,
variables = vars)
val futureTupleStatusCodeJValue = futureJValue.map(OK -> _).recover {
case error: QueryAnalysisError => BadRequest -> error.resolveError
case error: ErrorWithResolver => InternalServerError -> error.resolveError
}
complete(futureTupleStatusCodeJValue)
case Failure(error) =>
complete(BadRequest, error.getMessage)
}
route
}
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 using Play Framework 2.5 and trying to test one of my DAO class based on the example here.
But my class under test has dependency injection and I wonder how to mock it.
I tried to do something but it seems a bit clumsy, and I would like help to improve it.
Here is my code :
class MyController #Inject()(val mydao: MyDAOClass) extends Controller {
def getData = Action { request =>
mydao.fetchData
}
}
class MyDAOClass #Inject()(ws: WSClient) {
def urls(): List[String] = { List("url1", "url2") }
// fetch data on different servers and should merge it.
def fetchData(): Future[List[String]] = {
val futures: List[Future[WSResponse]] = urls map { url =>
ws.url(url + "/some/data").withRequestTimeout(10000.millis).get()
}
futures map { future =>
future onComplete {
case Success(resp) => println(resp.json)
case Failure(t) => println("An error has occured: " + t.printStackTrace)
}
}
Future(List())
}
}
Here is my test code :
trait DAOTrait {
def urls: List[String]
}
class MyDAOClass extends PlaySpec with MockitoSugar {
"MyDAOClass" should {
"get a list of data" in {
Server.withRouter() {
case GET(p"/url1/some/data") => Action {
Results.Ok(Json.arr(Json.obj("name" -> "data1")))
}
} { implicit port =>
val mockMyDAOClass = mock[DAOTrait]
when(mockMyDAOClass.urls) thenReturn List("url1")
implicit val materializer = Play.current.materializer
WsTestClient.withClient { client =>
val mydao = new MyDAOClass(client) {
override def urls = mockMyDAOClass.urls
}
val result = Await.result(
mydao.fetchData(), 10.seconds)
result mustEqual List("data1")
}
}
}
}
}
The code is not finished in MyDAOClass, but it is enough to start implementing tests.
1) How to mock class that have dependency injection ?
2) When testing this code I have the following exception in MyDAOClass case Failure(t)
java.net.ConnectException: http://localhost:40183
at org.asynchttpclient.netty.channel.NettyConnectListener.onFailure(NettyConnectListener.java:137)
at org.asynchttpclient.netty.request.NettyChannelConnector$1.onFailure(NettyChannelConnector.java:106)
at org.asynchttpclient.netty.SimpleChannelFutureListener.operationComplete(SimpleChannelFutureListener.java:28)
at org.asynchttpclient.netty.SimpleChannelFutureListener.operationComplete(SimpleChannelFutureListener.java:20)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:683)
at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:604)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:564)
at io.netty.util.concurrent.DefaultPromise.tryFailure(DefaultPromise.java:425)
at io.netty.channel.nio.AbstractNioChannel.doClose(AbstractNioChannel.java:462)
at io.netty.channel.socket.nio.NioSocketChannel.doClose(NioSocketChannel.java:236)
at io.netty.channel.AbstractChannel$AbstractUnsafe.doClose0(AbstractChannel.java:611)
at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:590)
at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:534)
at io.netty.channel.nio.NioEventLoop.closeAll(NioEventLoop.java:576)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:361)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.nio.channels.ClosedChannelException
but it should return Result.Ok instead.
3) Any advice welcome.
I want to write a unit test to test my custom action builder.
Once I set the ActionWithSession as in instance in my test, how do I instantiate it like it would be in a controller?
How can I test for the edge cases if it redirects or returns with the Session?
class SessionAwareRequest[A](val context: MyContext, request: Request[A]) extends WrappedRequest[A](request)
class ActionWithSession #Inject()(siteConfig: SiteConfig, userService: UserService)
extends ActionBuilder[SessionAwareRequest] {
def invokeBlock[A](request: Request[A], block: (SessionAwareRequest[A]) => Future[Result]) = {
val session = loadSession(request)
if(session.isDefined) {
val result: Future[Result] = block(new SessionAwareRequest(session.get, request))
result.map(_.withCookies(CookieHelper.newSession(session.get)(request)))
}
else {
Future.successful(Results.Redirect(routes.MessagesController.show("error!")))
}
def loadSession(requestHeader: RequestHeader): Option[MySession] = {
...
Some(MySession(...))
else
None
}
}
If you're using Play 2.3, you should be able to utilize the PlaySpecification trait, which provides a call method to test Actions. Have a look at the Documentation, especially the section about "Unit Testing EssentialAction
".
Here's an artificial, minimal example based on the code you posted. It basically just checks whether the request headers contains a "foo" field and if so, returns OK together with an additional cookie "baz" that is added via the custom action builder. If the "foo" header is missing it returns BadRequest. Hope it helps.
import play.api.mvc._
import play.api.test.{FakeRequest, PlaySpecification}
import scala.concurrent.Future
class ActionSpec extends PlaySpecification {
case class MySession()
class SessionAwareRequest[A](val context: MySession, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithSession extends ActionBuilder[SessionAwareRequest] {
def loadSession(requestHeader: RequestHeader): Option[MySession] = {
if (requestHeader.headers.get("foo").isDefined)
Some(MySession())
else
None
}
def invokeBlock[A](request: Request[A], block: (SessionAwareRequest[A]) => Future[Result]): Future[Result] = {
val session = loadSession(request)
if (session.isDefined) {
val result: Future[Result] = block(new SessionAwareRequest(session.get, request))
result.map(_.withCookies(Cookie("baz", "quux")))
} else {
Future.successful(Results.BadRequest)
}
}
}
"My custom action" should {
"return HTTP code 400 in case the request header does not contain a 'foo' field" in {
val foo = call(ActionWithSession { request =>
Results.Ok("Yikes")
}, FakeRequest())
status(of = foo) must beEqualTo(BAD_REQUEST)
cookies(of = foo).get("baz") must beNone
}
"return HTTP code 200 in case the request header does contain a 'foo' field" in {
val foo = call(ActionWithSession { request =>
Results.Ok("Yikes")
}, FakeRequest().withHeaders("foo" -> "bar"))
status(of = foo) must beEqualTo(OK)
cookies(of = foo).get("baz") must beSome
}
}
}