Scala/Android: You shouldn't mixin `org.specs2.matcher.MustExpectations` ... with `Scope`

797 views
Skip to first unread message

Austin Guest

unread,
May 8, 2015, 4:07:50 PM5/8/15
to specs2...@googlegroups.com
Hi all. I'm working on a location-sharing mobile app for protesters built on top of the excellent Macroid (a platform for writing Android Apps in Scala) and have hit a brick wall trying to put it under test in the most recent version of Specs2 (using the `3.6-scalaz-7.0.7` maven repo).

The wall looks like this: 

You shouldn't mixin `org.specs2.matcher.MustExpectations` or `org.specs2.matcher.ShouldExpectations` with `Scope`, use `org.specs2.matcher.MustThrownExpectations` or `org.specs2.matcher.ShouldThrownExpectations` instead.

Getting there looks like this:
 
The goal is to test a class called IntersectionService, which consumes the a lat-lon pair and produces the closest street corner (in English), by way of a geocoding API service. I've built an IntersectionServiceTest class to test it, cribbing off the ForecastServicesSpec and ForecastServices classes in 47 Degree's excellent collection of sample Macroid apps.

The test looks like this:

package org.tlc.whereat.services

import com.squareup.okhttp.OkHttpClient
import io.taig.communicator.internal.result.Parser
import org.specs2.mutable.Specification
import org.tlc.whereat.model.{ApiIntersection, Intersection, Loc}
import org.tlc.whereat.msg.IntersectionResponse
import org.tlc.whereat.support.{AppContextTestSupport, BaseTestSupport}

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}

trait IntersectionServiceMock
extends IntersectionService
with AppContextTestSupport {

val rcLoc = Loc(40.7206235,-74.0007963)
val rcLocReq = toIntersectionRequest(rcLoc)

val validIntersection = ApiIntersection (
lat = "40.72084",
lng = "-74.000661",
distance = "0.03",
street1 = "Broadway",
street2 = "Grand St ",
street1Bearing = "32",
street2Bearing = "124",
placename = "New York",
adminName1 = "New York",
adminName2 = "New York",
adminCode1 = "NY",
postalcode = "10013",
countryCode = "US",
mtfcc1 = "S1400",
mtfcc2 = "S1400"
)
}

class IntersectionServiceTest
extends Specification
with BaseTestSupport {

"Intersection Service" should {

"parse intersection from JSON" >>
new IntersectionServiceMock {

override def reqJson[T](url: String)(implicit parser: Parser[T], client: OkHttpClient = new OkHttpClient()): Future[T] =
Future.successful[T](validIntersection.asInstanceOf[T])

Await.result(IntersectionService.getIntersection(rcLocReq), Duration.Inf) must beEqualTo(
IntersectionResponse(Some(Intersection(street1 = "Grand St", street2 = "Broadway")))
)

}

}

}

When I run it, I get a runtime error with the following stack trace:

You shouldn't mixin `org.specs2.matcher.MustExpectations` or `org.specs2.matcher.ShouldExpectations` with `Scope`, use `org.specs2.matcher.MustThrownExpectations` or `org.specs2.matcher.ShouldThrownExpectations` instead.
java.lang.RuntimeException: You shouldn't mixin `org.specs2.matcher.MustExpectations` or `org.specs2.matcher.ShouldExpectations` with `Scope`, use `org.specs2.matcher.MustThrownExpectations` or `org.specs2.matcher.ShouldThrownExpectations` instead.
at org.specs2.matcher.Scope$class.$init$(ThrownExpectations.scala:133)
at org.tlc.whereat.services.IntersectionServiceTest$$anonfun$1$$anonfun$apply$1$$anon$1.<init>(IntersectionServiceTest.scala:52)
at org.tlc.whereat.services.IntersectionServiceTest$$anonfun$1$$anonfun$apply$1.apply(IntersectionServiceTest.scala:52)
at org.tlc.whereat.services.IntersectionServiceTest$$anonfun$1$$anonfun$apply$1.apply(IntersectionServiceTest.scala:52)
at org.specs2.matcher.Scope$$anon$3$$anonfun$asResult$1.apply(ThrownExpectations.scala:139)
at org.specs2.matcher.Scope$$anon$3$$anonfun$asResult$1.apply(ThrownExpectations.scala:139)
at org.specs2.execute.ResultExecution$class.execute(ResultExecution.scala:25)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:120)
at org.specs2.execute.Result$$anon$10.asResult(Result.scala:230)
at org.specs2.execute.AsResult$.apply(AsResult.scala:25)
at org.specs2.matcher.Scope$$anon$3.asResult(ThrownExpectations.scala:139)
at org.specs2.execute.AsResult$.apply(AsResult.scala:25)
at org.specs2.main.CommandLineAsResult$$anon$1.asResult(CommandLineAsResult.scala:17)
at org.specs2.main.CommandLineAsResult$$anonfun$apply$1.apply(CommandLineAsResult.scala:21)
at org.specs2.main.CommandLineAsResult$$anonfun$apply$1.apply(CommandLineAsResult.scala:21)
at org.specs2.specification.dsl.mutable.ExampleDsl1$BlockExample$$anonfun$$greater$greater$1.apply(ExampleDsl.scala:39)
at org.specs2.specification.dsl.mutable.ExampleDsl1$BlockExample$$anonfun$$greater$greater$1.apply(ExampleDsl.scala:39)
at org.specs2.specification.core.Execution$$anonfun$withEnv$1$$anonfun$apply$3.apply(Execution.scala:120)
at org.specs2.execute.ResultExecution$class.execute(ResultExecution.scala:25)
at org.specs2.execute.ResultExecution$.execute(ResultExecution.scala:120)
at org.specs2.execute.Result$$anon$10.asResult(Result.scala:230)
at org.specs2.execute.AsResult$.apply(AsResult.scala:25)
at org.specs2.specification.core.Execution$$anonfun$withEnv$1.apply(Execution.scala:120)
at org.specs2.specification.core.Execution$$anonfun$withEnv$1.apply(Execution.scala:120)
at org.specs2.specification.core.Execution$$anonfun$execute$2$$anonfun$apply$2.apply(Execution.scala:70)
at org.specs2.specification.core.Execution$$anonfun$execute$2$$anonfun$apply$2.apply(Execution.scala:70)
at org.specs2.specification.core.Execution.setResult(Execution.scala:76)
at org.specs2.specification.core.Execution$$anonfun$execute$2.apply(Execution.scala:70)
at org.specs2.specification.core.Execution$$anonfun$execute$2.apply(Execution.scala:70)
at scala.Option.fold(Option.scala:158)
at org.specs2.specification.core.Execution.execute(Execution.scala:70)
at org.specs2.specification.process.DefaultExecutor$$anonfun$executeFragment$1$$anonfun$apply$6.apply(Executor.scala:132)
at org.specs2.specification.process.DefaultExecutor$$anonfun$executeFragment$1$$anonfun$apply$6.apply(Executor.scala:130)
at org.specs2.specification.core.Fragment.updateExecution(Fragment.scala:44)
at org.specs2.specification.process.DefaultExecutor$$anonfun$executeFragment$1.apply(Executor.scala:130)
at org.specs2.specification.process.DefaultExecutor$$anonfun$executeFragment$1.apply(Executor.scala:129)
at org.specs2.specification.process.DefaultExecutor$$anonfun$sequencedExecution$1.executedFragment$lzycompute$1(Executor.scala:104)
at org.specs2.specification.process.DefaultExecutor$$anonfun$sequencedExecution$1.org$specs2$specification$process$DefaultExecutor$class$$anonfun$$executedFragment$1(Executor.scala:104)
at org.specs2.specification.process.DefaultExecutor$$anonfun$sequencedExecution$1$$anonfun$4.apply(Executor.scala:110)
at org.specs2.specification.process.DefaultExecutor$$anonfun$sequencedExecution$1$$anonfun$4.apply(Executor.scala:110)
at scalaz.concurrent.Task$.Try(Task.scala:299)
at org.specs2.data.Processes$$anonfun$start$1.apply(Processes.scala:96)
at org.specs2.data.Processes$$anonfun$start$1.apply(Processes.scala:96)
at scalaz.concurrent.Future$$anonfun$apply$13$$anon$3.call(Future.scala:325)
at scalaz.concurrent.Future$$anonfun$apply$13$$anon$3.call(Future.scala:325)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)

That error originates from this line in the Specs2 source. I can't for the life of me figure out why I'm getting it. My best guess is that it's because one of my support files (AppContextTestSupport) mixes in specs2.mock.Mockito (which extends `Expectations`) and Scope. 

BUT if I clone the 47 Degrees repo that has a nearly identical support setup (including the same version of AppContextTestSupport), all tests pass without a hiccup. Perhaps there's some small difference I'm not seeing? Maybe it has something to do with dependency mismatches in my build?

This is my build file:

import android.Dependencies.aar
import android.Keys._

android.Plugin.androidBuild

platformTarget in Android := "android-21"

name := "whereat"

scalaVersion := "2.11.6"

run <<= run in Android

resolvers ++= Seq(
Resolver.sonatypeRepo("releases"),
Resolver.mavenLocal,
"47deg Public" at "http://clinker.47deg.com/nexus/content/groups/public",
"jcenter" at "http://jcenter.bintray.com",
"scalaz-bintray" at "http://dl.bintray.com/scalaz/releases"
)

scalacOptions in (Compile, compile) ++=
(dependencyClasspath in Compile).value.files.map("-P:wartremover:cp:" + _.toURI.toURL)

scalacOptions in (Compile, compile) ++= Seq(
"-P:wartremover:traverser:macroid.warts.CheckUi"
)


scalacOptions ++= Seq("-feature", "-deprecation", "-target:jvm-1.7")

scalacOptions in Test ++= Seq("-Yrangepos")

javacOptions ++= Seq("-source", "1.7", "-target", "1.7")

libraryDependencies ++= Seq(
aar("org.macroid" %% "macroid" % "2.0.0-M4"),
aar("org.macroid" %% "macroid-viewable" % "2.0.0-M4"),
aar("com.android.support" % "support-v4" % "21.0.3"),
aar("com.fortysevendeg" %% "macroid-extras" % "0.1-SNAPSHOT"),
"com.google.android.gms" % "play-services" % "6.5.87",
"io.taig" %% "communicator" % "2.0.1",
"com.typesafe.play" %% "play-json" % "2.3.4",
"org.specs2" % "specs2-core_2.11" % "3.6-scalaz-7.0.7" % "test",
"org.specs2" % "specs2-mock_2.11" % "3.6-scalaz-7.0.7" % "test",
"org.specs2" % "specs2-junit_2.11" % "3.6-scalaz-7.0.7" % "test",
// "org.specs2" %% "specs2-core" % "2.4.17" % "test",
// "org.specs2" % "specs2-mock_2.11" % "3.0-M2" % "test",
"com.google.android" % "android" % "4.1.1.4" % "test",
compilerPlugin("org.brianmckenna" %% "wartremover" % "0.10")
)

proguardScala in Android := true

proguardOptions in Android ++= Seq(
"-ignorewarnings",
"-keep class scala.Dynamic"
)

apkbuildExcludes in Android ++= Seq (
"META-INF/LICENSE",
"META-INF/LICENSE.txt",
"META-INF/NOTICE",
"META-INF/NOTICE.txt"
)


The commented lines were abortive attempts to see if using the same versions of specs2 as the 47 Degrees project uses (see here, here, and here) would fix the problem. It didn't!

Just for good measure, here are all the support files involved in the spec in question:

* AppContextSupport:

package org.tlc.whereat.support

import com.fortysevendeg.macroid.extras.AppContextProvider
import macroid.AppContext
import org.specs2.mock.Mockito
import org.specs2.specification.Scope

trait AppContextTestSupport
extends Mockito
with AppContextProvider
with TestConfig
with Scope {


implicit val appContextProvider: AppContext = mock[AppContext]

appContextProvider.get returns mockContext

}

* AppContextProvider:

package com.fortysevendeg.macroid.extras

import macroid.AppContext

trait AppContextProvider {
implicit val appContextProvider : AppContext
}


* TestConfig:

package org.tlc.whereat.support

import android.content.Context
import org.specs2.mock.Mockito

trait TestConfig extends Mockito {
val mockContext = mock[Context]
}



I'm working in IntelliJ 14 Ultimate, which has provided a host of headaches with regards SBT integration, but at the moment, I'm running my tests directly with `sbt test` and still getting the same errors as running through IntelliJ. In sum: I've tried just about everything and am completely stumped.

Can anyone help point me in the right direction? Perhaps just start by explaining what that error message is trying to say and how I might go about following its suggestion to use `MustThrownMatchers`? Would I need to mix in a different trait somewhere? Use a different operator in the actual specs? Construct my specs and mocks differently? Or am I totally on the wrong track?

Any help welcome,

Austin

@aguestuser | aguestuser
LittleSis.org -- software engineer
TheLearningCollective.nyc -- co-founder
Recurse Center -- alum


etorreborre

unread,
May 11, 2015, 12:49:37 AM5/11/15
to specs2...@googlegroups.com, guest....@gmail.com
Sorry for the quick read but did you try to use the following trait instead?

trait AppContextTestSupport
  extends Mockito
  with AppContextProvider
  with TestConfig
  with Scope 
  with org.specs2.matcher.MustThrownExpectations

If you don't throw expectations when using a Scope your tests will always pass. The Mockito trait extends the Expectations trait to be able to create Expectations from assertions on mock objects but by default no exception is thrown when there is a failure.
Adding org.specs2.matcher.MustThrownExpectations to AppContextTestSupport ensures that when expectations are created they are wired up to throw exceptions in case of a failure.

Please tell me if the trait above doesn't work.

Eric.

Austin Guest

unread,
May 11, 2015, 2:01:16 AM5/11/15
to specs2...@googlegroups.com
That worked! Thanks so much!

PS: This line of inquiry (trying to use Specs2 for Scala/Android tests) caused me to dig into this epic thread about the struggle to get Specs2 working with Robolectric, with the result that the robospecs library might be soon getting an update. Will keep you posted (and perhaps have some questions along the way!)

Austin

--
You received this message because you are subscribed to a topic in the Google Groups "specs2-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/specs2-users/SNelF828648/unsubscribe.
To unsubscribe from this group and all its topics, send an email to specs2-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Reply all
Reply to author
Forward
0 new messages