I'm trying to verify that a function in a router is being called but everytime I do, I get this error saying that there were no interactions with the mock object.
The function I would like to verify is the doStuff function so I'm mocking the modelService object.
This is my setup:
//Router
val foo(user: String, _: String, containerId: Long) => pathPrefix("foo"){
path("bar") {
post {
entity(as[someJson]) { report =>
val actions = () => (for {
msg <- eitherT((someActor? reportFunction(report.name, report.modelsTasks, user)).mapTo[\/[String, reportJson]])
id <- eitherT(Future.successful(msg.id.toRightDisjunction("Cannot obtain id")))
model <- eitherT(modelService.doStuff(user, id, containerId, report.asReportComponent()).
map(_.right[String]))
} yield model).run
handleResult(actions())
}
}
}
}
\\Test
"do some stuff when asked" in {
when(modelService.doStuff(eqTo("unknown"),
any[String], eqTo(1L), any[reportComponent]))
.thenReturn(Future.successful(expectedApp))
Post("/foo/bar", someJson("Test")) ~>
foo("unknown", "127.0.0.1", 1L) ~> check {
verify( modelService, times(1) ).doStuff(eqTo("unknown"), any[String], eqTo(1L), any[reportComponent])
}
}
But each time I run my test, I get something like this
Wanted but not invoked:
modelService.doStuff(
"unknown",
<any>,
1,
<any>
);
-> at com.this.test.spec.apply(ThisTestSpec.scala:112)
Actually, there were zero interactions with this mock.
How are you injecting the mock? could you post how you create your router class in the test?
Also, did you check that msg <- eitherT and id <- eitherT are returning a valid value? otherwise the modelService is never going to be called
Related
I wrote simple callback(handler) function which i pass to async api and i want to wait for result:
object Handlers {
val logger: Logger = Logger("Handlers")
implicit val cs: ContextShift[IO] =
IO.contextShift(ExecutionContext.Implicits.global)
class DefaultHandler[A] {
val response: IO[MVar[IO, A]] = MVar.empty[IO, A]
def onResult(obj: Any): Unit = {
obj match {
case obj: A =>
println(response.flatMap(_.tryPut(obj)).unsafeRunSync())
println(response.flatMap(_.isEmpty).unsafeRunSync())
case _ => logger.error("Wrong expected type")
}
}
def getResponse: A = {
response.flatMap(_.take).unsafeRunSync()
}
}
But for some reason both tryPut and isEmpty(when i'd manually call onResult method) returns true, therefore when i calling getResponse it sleeps forever.
This is the my test:
class HandlersTest extends FunSuite {
test("DefaultHandler.test") {
val handler = new DefaultHandler[Int]
handler.onResult(3)
val response = handler.getResponse
assert(response != 0)
}
}
Can somebody explain why tryPut returns true, but nothing puts. And what is the right way to use Mvar/channels in scala?
IO[X] means that you have the recipe to create some X. So on your example, yuo are putting in one MVar and then asking in another.
Here is how I would do it.
object Handlers {
trait DefaultHandler[A] {
def onResult(obj: Any): IO[Unit]
def getResponse: IO[A]
}
object DefaultHandler {
def apply[A : ClassTag]: IO[DefaultHandler[A]] =
MVar.empty[IO, A].map { response =>
new DefaultHandler[A] {
override def onResult(obj: Any): IO[Unit] = obj match {
case obj: A =>
for {
r1 <- response.tryPut(obj)
_ <- IO(println(r1))
r2 <- response.isEmpty
_ <- IO(println(r2))
} yield ()
case _ =>
IO(logger.error("Wrong expected type"))
}
override def getResponse: IO[A] =
response.take
}
}
}
}
The "unsafe" is sort of a hint, but every time you call unsafeRunSync, you should basically think of it as an entire new universe. Before you make the call, you can only describe instructions for what will happen, you can't actually change anything. During the call is when all the changes occur. Once the call completes, that universe is destroyed, and you can read the result but no longer change anything. What happens in one unsafeRunSync universe doesn't affect another.
You need to call it exactly once in your test code. That means your test code needs to look something like:
val test = for {
handler <- TestHandler.DefaultHandler[Int]
_ <- handler.onResult(3)
response <- handler.getResponse
} yield response
assert test.unsafeRunSync() == 3
Note this doesn't really buy you much over just using the MVar directly. I think you're trying to mix side effects inside IO and outside it, but that doesn't work. All the side effects need to be inside.
I am using Akka HTTP cache to cache my result. But i am facing issue to test it.
class GoogleAnalyticsController #Inject()(cache: Cache[String, HttpResponse],
googleAnalyticsApi: GoogleAnalyticsTrait,
googleAnalyticsHelper: GoogleAnalyticsHelper)
(implicit system: ActorSystem, materializer: ActorMaterializer) {
def routes: Route =
post {
pathPrefix("pageviews") {
path("clients" / Segment) { accountsClientId =>
entity(as[GoogleAnalyticsMetricsRequest]) { googleAnalyticsMetricsRequest =>
val googleAnalyticsMetricsKey = "key"
complete(
cache.getOrLoad(googleAnalyticsMetricsKey, _ => getGoogleAnalyticsMetricsData(accountsClientId, googleAnalyticsMetricsRequest))
)
}
}
}
}
private def getGoogleAnalyticsMetricsData(accountsClientId: String,
request: GoogleAnalyticsMetricsRequest) = {
val payload = generate(request)
val response = googleAnalyticsApi.googleAnalyticsMetricResponseHandler(accountsClientId, payload) // response from another microservice
googleAnalyticsHelper.googleAnalyticsMetricResponseHandler(
googleAnalyticsMetricsRequest.metricName, response)
}
}
class GoogleAnalyticsHelper extends LoggingHelper {
def googleAnalyticsMetricResponseHandler(metricName: String, response: Either[Throwable, Long]): Future[HttpResponse] =
response.fold({ error =>
logger.error(s"An exception has occurred while getting $metricName from behavior service and error is ${error.getMessage}")
Marshal(FailureResponse(error.getMessage)).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.InternalServerError))
}, value =>
Marshal(MetricResponse(metricName, value)).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.OK))
)
}
Test case: Sharing only the relevant part
"get success metric response for " + pageviews + " metric of given accounts client id" in { fixture =>
import fixture._
val metricResponse = MetricResponse(pageviews, 1)
val eventualHttpResponse = Marshal(metricResponse).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.OK))
when(cache.getOrLoad(anyString, any[String => Future[HttpResponse]].apply)).thenReturn(eventualHttpResponse)
when(googleAnalyticsApi.getDataFromGoogleAnalytics(accountsClientId, generate(GoogleAnalyticsRequest(startDate, endDate, pageviews))))
.thenReturn(ApiResult[Long](Some("1"), None))
when(googleAnalyticsHelper.googleAnalyticsMetricResponseHandler(pageviews, Right(1))).thenReturn(eventualHttpResponse)
Post(s"/pageviews/clients/$accountsClientId").withEntity(requestEntity) ~>
googleAnalyticsController.routes ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual generate(metricResponse)
}
}
By doing this, I am best to test if the cache has the key but not able to test if cache misses the hit. In code coverage, it misses following highlighted part
cache.getOrLoad(googleAnalyticsMetricsKey, _ =>
getGoogleAnalyticsMetricsData(accountsClientId,
googleAnalyticsMetricsRequest))
If there is a design issue, please feel free to guide me on how can I make my design testable.
Thanks in advance.
I think you don't need to mock the cache. You should create an actual object for cache instead of mocked one.
What you have done is, you have mocked the cache, in this case, the highlighted part will be not called as you are providing the mocked value. In the following stubbing, whenever cache.getOrLoad is found, eventualHttpResponse is returned:
when(cache.getOrLoad(anyString, any[String => Future[HttpResponse]].apply)).thenReturn(eventualHttpResponse)
and hence the function getGoogleAnalyticsMetricsData(accountsClientId, googleAnalyticsMetricsRequest) is never called.
I have a class with a single method that I want to unit-test:
#Singleton
class RegistrationWorkflow #Inject()(userService: UserService,
addUserValidator: RegisterUserValidator,
db: Database) {
def registerUser(registerForm: RegisterUserForm): Future[Vector[FormError]] = {
val dbActions = addUserValidator.validate(registerForm).flatMap({ validation =>
if (validation.isEmpty) {
userService.add(User(GUID.shortGuid(),
registerForm.username,
registerForm.email,
BCrypt.hashpw(registerForm.password, BCrypt.gensalt())))
.andThen(DBIO.successful(validation))
} else {
DBIO.successful(validation)
}
}).transactionally
db.run(dbActions)
}
}
addUserValidator validates the form and returns a Vector of form errors. If there were no errors, the user is inserted into database. I am returning the form errors because in the controller I'm either returning a 201 or a 400 with a list of errors.
I have written a specs2 test for this:
class RegistrationWorkflowTest extends Specification with Mockito with TestUtils {
"RegistrationControllerWorkflow.registerUser" should {
"insert a user to database if validation succeeds" in new Fixture {
registerUserValidatorMock.validate(testUserFormData) returns DBIO.successful(Vector())
userServiceMock.add(any) returns DBIO.successful(1)
val result = await(target.registerUser(testUserFormData))
result.isEmpty must beTrue
there was one(registerUserValidatorMock).validate(testUserFormData)
there was one(userServiceMock).add(beLike[User] { case User(_, testUser.username, testUser.email, _) => ok })
}
"return error collection if validation failed" in new Fixture {
registerUserValidatorMock.validate(testUserFormData) returns DBIO.successful(Vector(FormError("field", Vector("error"))))
val result = await(target.registerUser(testUserFormData))
result.size must beEqualTo(1)
result.contains(FormError("field", Vector("error"))) must beTrue
there was one(registerUserValidatorMock).validate(testUserFormData)
there was no(userServiceMock).add(any)
}
}
trait Fixture extends Scope with MockDatabase {
val userServiceMock = mock[UserService]
val registerUserValidatorMock = mock[RegisterUserValidator]
val target = new RegistrationWorkflow(userServiceMock, registerUserValidatorMock, db)
val testUser = UserFactory.baseUser()
val testUserFormData = RegisterUserFactory.baseRegisterUserForm()
}
}
The issue with this test is that it just asserts that userService.add was called. This means that I can change my implementation to the following:
val dbActions = addUserValidator.validate(registerForm).flatMap({ validation =>
if (validation.isEmpty) {
userService.add(User(GUID.shortGuid(),
registerForm.username,
registerForm.email,
BCrypt.hashpw(registerForm.password, BCrypt.gensalt())))
DBIO.successful(validation)
} else {
DBIO.successful(validation)
}
}).transactionally
db.run(dbActions)
The test still passes, but the user will not be inserted, because I am not ussing andThen combinator on the DBIO that was returned by userService.add method.
I know that I could use an in memory database and then assert that the user was actually inserted, but I don't want to do that because I already tested userService.add method separately with an in memory database and now I want to test registerUser method without calling any dependencies.
Appears I am missing something but here is what I have got (posting only relevant piece). where MyService.save returns Future[Option[MyCompany] ].
def myPost = Action.async(parse.json) { request =>
val mn = Json.fromJson[MyEntity](request.body)
mn.map{
case m : MyEntity => MyService.save(m).map{f=>
f.map(mm=> Ok(mm ) )
}
}.recoverTotal {
e => Future { BadRequest("Detected error:" + JsError.toFlatJson(e)) }
}
}
Although I have defined
implicit val companyWriter: Writes[MyCompany] = (...)
And this implicit is in the scope, it shows compile error
Cannot write an instance of MyCompany to HTTP response. Try to define
a Writeable[MyCompany]
FYI: This writer is used elsewhere where I do Json.toJson(myCompany) and over there it finds and works fine.
Anything in particular to async Ok that it's missing?
EDIT
It appears that Ok() method cannot figure out the MyCompany needs to be transformed to json. following seems to have worked.
Ok(Json.toJson(mm) )
Is this because arguments to Ok can vary? Separately there are too many "map" in the above code. Any recommendation for improvement and making it more concise ?
Your compiler error is about a Writeable, not a Writes. Writeables are used to convert whatever you have to something that can be written to an HTTP response, Writes are used to marshall objects to JSON. The names can be a little confusing.
As for style...
def myPost = Action.async(parse.json) { request =>
request.body.validate[MyEntity] map { myEntity =>
MyService.save(myEntity).map { maybeCompany =>
maybeCompany match {
case Some(company) => Ok(Json.toJson(company))
case None => NoContent // or whatever's appropriate
}
}
} recoverTotal { t =>
Future { BadRequest("Detected error: " + JsError.toFlatJson(e)) }
}
}
Say I have an object with a method called protect.
This is supposed to take a function with JValue => JValue(non generic parameters).
def protect(func: (JValue) => JValue) = {
if (Something.is) {
InMemoryResponse(Array(), Nil, Nil, 200)
} else {
InMemoryResponse(Array(), Nil, Nil, 401)
}
}
How can I now call the function passing its original argument?
The goal is to check if a user session exists before responding to a REST request. If it doesn't then a 401 Forbidden will be returned automatically, so something like the following. In this case I want to pass Users.getUsers as the argument function and limit as the param to the function.
So inside protect, if the Session exists the function will execute and display.
serve {
case "user" :: limit :: Nil JsonGet _ =>
AuthorizedReq.protect(json => Users.getUsers(limit))
I ended up using this:
object AuthorizedReq extends Logger {
private[this] val error: JValue = ("error" -> "Unauthorized request");
def protect(func: (JValue) => JValue, json: JValue): JValue = {
if (LoginSession.is) {
new OkResponse
func(json)
} else {
new UnauthorizedResponse
error;
}
}
}
}
In combination with:
serve {
case "user" :: limit :: Nil JsonPost json -> _ =>
for {
json <- AuthorizedReq.protect(json => Users.getUsers(json), json)
} yield json: JValue
}
If the user is not authenticated, server responds with 401, otherwise the original function executes. It should be easy enough to introduce roles and realms.
This replaces Lift's default auth mechanism, which lacks documentation.
As far as I can see you're not actually passing the function Users.getUsers as a parameter. You're passing a function which calls this function, using the limit parameter.
Because when you create the anonymous function that you're passing as a parameter you also create a closure, the value of limit is still available to the function (although not visible to you). You should be able to just call func with no arguments and have it work fine.