Mocking service calls

409 views
Skip to first unread message

Tim Pigden

unread,
Feb 21, 2017, 12:58:13 PM2/21/17
to Lagom Framework Users
I have 2 Aggregate Roots:
Products and Orders
Each order contains a reference (id) of the associated product.

My addOrder call on the OrderService needs to verify that the referenced product exists. The contents of addOrder could come from an external source, so there is no guarantee that the product id is actually valid.

Now I can see that I could get hold of the product service and call it directly as a check (or at least I assume that's straight-forward). But I know from previous questions we can't run 2 services in a single test (or at least that's what I understood to be the case). So how can I mock the product service in a unit test. Has this been done? Are there any examples? Or do I have to do it as part of an integration test against a and accept that I can't unit test at this level?


James Roper

unread,
Feb 21, 2017, 8:10:09 PM2/21/17
to Tim Pigden, Lagom Framework Users
Hi Tim,

You should be able to mock a service call by just implementing it.  You could also use Mockito if you don't want to implement every method on it.  But let's say you have this interface for ProductService, and this implementation for OrderService, and the following application cake:

trait ProductService {
  def getProduct(id: String): ServiceCall[NotUsed, Product]

  override def descriptor = ...
}

class OrderServiceImpl(productService: ProductService)(implicit ec: ExecutionContext) extends OrderService {
  override def addProductToOrder(orderId: String, productId: String) = ServiceCall { _ =>
    productService.getProduct(productId).map { product =>
      ...
    }
  }
}

class OrderApplication(ctx: LagomContext) extends LagomApplication(ctx) 
  with AhcWSComponents {

  lazy val productService = serviceClient.implement[ProductService]
  override lazy val lagomServer = LagomServer.forServices(bindService[OrderService].to(wire[OrderServiceImpl]))
}

Now, in your test you want to mock product service, so you simply override it in your cake:

class OrderServiceTest extends WorkSpec with Matchers with BeforeAndAfterAll {
  lazy val server = ServiceTest.startServer(ServiceTest.defaultSetup) { ctx =>
    new OrderApplication(ctx) with LocalServiceLocator {

      // Here is your mocked product service. If you would rather use mockito,
      // you could also at this point return a mockito implemented product
      // service.
      override lazy val productService = new ProductService {
        override def getProduct(id: String) = ServiceCall { _ =>
          id match {
            case "123" => Future.successful(new Product(...))
            case "456" => throw NotFound()
          }
        }
    }
  }

  lazy val client = server.serviceClient.implement[OrderService]}
  
  ...
}


Regards,

James

--
You received this message because you are subscribed to the Google Groups "Lagom Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lagom-framework+unsubscribe@googlegroups.com.
To post to this group, send email to lagom-framework@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/lagom-framework/6c0445bb-17be-42d6-9791-5aa242c678f9%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
James Roper
Software Engineer

Lightbend – Build reactive apps!
Twitter: @jroper

Tim Pigden

unread,
Feb 22, 2017, 12:03:07 PM2/22/17
to Lagom Framework Users, tim.p...@optrak.com
Thanks, works a treat
To unsubscribe from this group and stop receiving emails from it, send an email to lagom-framewo...@googlegroups.com.
To post to this group, send email to lagom-f...@googlegroups.com.

Joo Lee

unread,
Apr 6, 2017, 11:22:23 PM4/6/17
to Lagom Framework Users, tim.p...@optrak.com
How about using Mockito like this?

val mockedServiceCallForOrderSvc = mock[ServiceCall[NotUsed, Order]]
when(mockedServiceCallForOrderSvc.invoke()).thenReturn(Future.successful(someOrderObject))
when(orderSvcMock.getProduct(any())).thenReturn(mockedServiceCallForOrderSvc)

James Roper

unread,
Apr 7, 2017, 12:10:01 AM4/7/17
to Joo Lee, Lagom Framework Users, Tim Pigden
On 7 April 2017 at 13:22, Joo Lee <j...@stashaway.com> wrote:
How about using Mockito like this?

val mockedServiceCallForOrderSvc = mock[ServiceCall[NotUsed, Order]]
when(mockedServiceCallForOrderSvc.invoke()).thenReturn(Future.successful(someOrderObject))
when(orderSvcMock.getProduct(any())).thenReturn(mockedServiceCallForOrderSvc)


No reason why you can't do that, but which is simpler:

val mockedServiceCallForOrderSvc = mock[ServiceCall[NotUsed, Order]]
when(mockedServiceCallForOrderSvc.invoke()).thenReturn(Future.successful(someOrderObject))

or

val mockedServiceCallForOrderSvc = ServiceCall(_ => Future.successful(someOrderObject))

The former may be useful if you want to capture or otherwise run assertions on the argument passed into invoke.  But if you're not interested in that, then I see no reason to use mockito to mock it when you can implement a mock yourself in one line.
To unsubscribe from this group and stop receiving emails from it, send an email to lagom-framework+unsubscribe@googlegroups.com.
To post to this group, send email to lagom-framework@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/lagom-framework/41c4016d-c502-4466-8431-9742ba7cd187%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Tim Pigden

unread,
Apr 7, 2017, 4:19:53 AM4/7/17
to James Roper, Joo Lee, Lagom Framework Users
For our multi-service development we've taken the route of using a map-backed mock service. In fact, because, we will need a mock for virtually every service the process of a new entity (ok so we've only done about a dozen so far, but it's just me and a couple of interns) is
1. design the api
2. write the mock
3. do service tests against the mock
(or 3 then 2 if we're being virtuous)
4. back fill with the real service.
The mock then both performs as part of a TDD approach and is available asap for other services that might need to use it.
In test the mock can be supplied with the mocks from the other services that it depends upon.

It gives you the ability to write and test all the service logic that is not just about storing data and retrieving queries at an early stage too - and finally the service tests can be designed as a trait that can then be called from a simple mock intialisation or from a heavier duty full service initialisation.

Since service APIs evolve most rapidly at the start - especially when used from other services - we are in a good position to get the service definition right at a state where it's very cheap. When we did service before mock, any api change necessitated new commands and events and event handlers and mods to the CQL in the repository, the entity test, the repository test and all that stuff- which is highly time consuming.


--
Tim Pigden
Optrak Distribution Software Limited
+44 (0)1992 517100
http://www.linkedin.com/in/timpigden
http://optrak.com
Optrak Distribution Software Ltd is a limited company registered in England and Wales.
Company Registration No. 2327613 Registered Offices: Suite 6,The Maltings, Hoe Lane, Ware, SG12 9LR England 
This email and any attachments to it may be confidential and are intended solely for the use of the individual to whom it is addressed. Any views or opinions expressed are solely those of the author and do not necessarily represent those of Optrak Distribution Software Ltd. If you are not the intended recipient of this email, you must neither take any action based upon its contents, nor copy or show it to anyone. Please contact the sender if you believe you have received this email in error.
Reply all
Reply to author
Forward
0 new messages