Here you can find out how to create integration tests for RESTful service on the example of application shown in the
article "Building REST service
with Scala". To create the tests, I am going to use spray-testkit DSL, as it provides a simple way to test
route logic for services built with spray-routing.
Also, to keep everything up to date, I've updated the RESTful service built in the article
to use Spray version 1.2.1. You can find the updated code that works with newer versions of Spray, Akka and Lift JSON
here.
To run the
service and be sure that everything works correctly, specify the right mysql parameters in the
src/main/resources/application.conf file and then create appropriate MySQL database.
So, let's get to implementing the tests:
Step 1. Configure dependencies
At first, configure build.sbt to use spray-testkit with specs2 by adding the following dependencies:
I've placed the implicits in Customer.scala, but you can use a separate file.
Create the CustomerServiceTestBase trait in the test folder, it will be the base trait for our tests.
CustomerServiceTestHelper should extend: Specification (to use specs2 matchers), Specs2RouteTest (to use spray routing DSL) and
HttpService to connect the DSL to the test ActorSystem by setting actorRefFactory. Also, it should mixin the specs2
Before trait to run the code which cleans the database before test execution.
Then add the actorRefFactory to connect the DSL to the test ActorSystem, implement the service initialization to
get the spray route instance for test requests, and create the following methods:
cleanDB - to clean customers table before test execution
implicit HttpEntityToListOfCustomers - to convert JSON response from service to List[Customer]
implicit HttpEntityToErrors - to convert JSON response with error description to Map[String, String]
importscala.language.existentialsimportorg.specs2.mutable.{Before,Specification}importspray.testkit.Specs2RouteTestimportspray.routing.HttpServiceimportakka.actor.ActorRefFactoryimportspray.http.{HttpCharsets,HttpEntity}importnet.liftweb.json.Serializationimportcom.sysgears.example.domain.{Customers,Customer}importcom.sysgears.example.domain.CustomerConversions._importcom.sysgears.example.config.Configurationimportscala.slick.session.Databaseimportscala.slick.driver.MySQLDriver.simple.Database.threadLocalSessionimportscala.slick.driver.MySQLDriver.simple._importslick.jdbc.meta.MTabletraitCustomerServiceTestBaseextendsSpecificationwithSpecs2RouteTestwithHttpServicewithConfigurationwithBefore{// makes test execution sequential and prevents conflicts that may occur when the data is// changed simultaneously in the databaseargs(sequential=true)valcustomerLink="/customer"// connects the DSL to the test ActorSystemimplicitdefactorRefFactory=systemvalspec=thisvalcustomerService=newRestService{overrideimplicitdefactorRefFactory:ActorRefFactory=spec.actorRefFactory}.rest/** * Cleans the database before test execution. */defcleanDB()={// inits database instancevaldb=Database.forURL(url=s"jdbc:mysql://$dbHost:$dbPort/$dbName",user=dbUser,password=dbPassword,driver="com.mysql.jdbc.Driver")// drops tables if existdb.withSession{if(MTable.getTables("customers").list().nonEmpty){Customers.ddl.dropCustomers.ddl.create}}}// converts responses from the serviceimplicitdefHttpEntityToListOfCustomers(httpEntity:HttpEntity)=Serialization.read[List[Customer]](httpEntity.asString(HttpCharsets.`UTF-8`))implicitdefHttpEntityToErrors(httpEntity:HttpEntity)=Serialization.read[Map[String, String]](httpEntity.asString(HttpCharsets.`UTF-8`))}
Step 4. Create test data
Obviously, we need test data to run the tests, so let's create the CustomerTestData object with
the list of two consumers and the date formatter for responses:
importjava.text.SimpleDateFormatimportcom.sysgears.example.domain.CustomerobjectCustomerTestData{valdateFormat=newSimpleDateFormat("yyyy-MM-dd")// the test datavalbirthday0=Some(dateFormat.parse("1991-01-14"))valfirstName0="Andrey"vallastName0="Litvinenko"valbirthday1=Some(dateFormat.parse("1987-01-14"))valfirstName1="Corwin"vallastName1="Holmes"valcustomersIds=List(1,2)valcustomers=List(Customer(Some(customersIds(0)),firstName0,lastName0,birthday0),Customer(Some(customersIds(1)),firstName1,lastName1,birthday1))valnonExistentCustomerId=customers.size+1}
If you need more data for tests, you might want to create a function to generate random data.
Step 5. Create tests
Now we are finally ready for writing the tests.
All the tests follow a certain template:
123456789
"service"should{"do something"in{// action descriptionrequest~>service~>check{// request to service// response checks:statusshouldbeequalTo???entityshouldbeequalTo???}}}
We are going to clean the DB before running the tests, and then test the main service functionality by sending CRUD
requests and checking the responses.
Here is the shortened version of tests, you can see the full version in
repository:
importspray.http.HttpEntityimportnet.liftweb.json.Serializationimportspray.http.HttpMethods._importspray.http.StatusCodes._importcom.sysgears.example.domain.Customer// imports the object with test data:importcom.sysgears.example.rest.CustomerTestData._importcom.sysgears.example.domain.CustomerConversions._importspray.http.HttpRequestimportscala.SomeclassCustomerServiceSpecextendsCustomerServiceTestBase{defbefore=cleanDB()"Customer service"should{"post customers"in{HttpRequest(POST,customerLink,entity=HttpEntity(Serialization.write(customers(0))))~>customerService~>check{response.statusshouldbeequalToCreatedresponse.entityshouldnotbeequalTo(None)valrespCustomer=responseAs[Customer]respCustomer.id.getmustbegreaterThan0respCustomermustbeequalTocustomers(0)}HttpRequest(POST,customerLink,entity=HttpEntity(Serialization.write(customers(1))))~>customerService~>check{response.statusshouldbeequalToCreatedresponse.entityshouldnotbeequalTo(None)valrespCustomer=responseAs[Customer]respCustomer.id.getmustbegreaterThan0respCustomermustbeequalTocustomers(1)}}"return list of posted customers"in{Get(customerLink)~>customerService~>check{response.statusshouldbeequalToOKresponse.entityshouldnotbeequalTo(None)valrespCustomers=responseAs[List[Customer]]respCustomers.sizeshouldbeequalTo2respCustomers(0)mustbeequalTocustomers(0)respCustomers(1)mustbeequalTocustomers(1)}}"search for customers"in{"search by firstName"in{Get(s"$customerLink?firstName=$firstName0")~>customerService~>check{response.statusshouldbeequalToOKresponse.entityshouldnotbeequalTo(None)valrespCustomer=responseAs[Customer]respCustomermustbeequalTocustomers(0)}}}"return customer by id"in{Get(s"$customerLink/${customersIds(0)}")~>customerService~>check{response.statusshouldbeequalToOKresponse.entityshouldnotbeequalTo(None)responseAs[Customer]mustbeequalTocustomers(0)}}"update customer data"in{HttpRequest(PUT,s"$customerLink/${customersIds(0)}",entity=HttpEntity(Serialization.write(customers(1))))~>customerService~>check{response.statusshouldbeequalToOKresponse.entityshouldnotbeequalTo(None)responseAs[Customer].copy(id=Some(customersIds(1)))mustbeequalTocustomers(1)}Get(s"$customerLink/${customersIds(0)}")~>customerService~>check{response.statusshouldbeequalToOKresponse.entityshouldnotbeequalTo(None)responseAs[Customer].copy(id=Some(customersIds(1)))mustbeequalTocustomers(1)}}"delete created customers by id"in{customersIds.map{id=>Delete(s"$customerLink/$id")~>customerService~>check{response.statusshouldbeequalToOKresponse.entityshouldnotbeequalTo(None)}}.find(!_.isSuccess)===None}"return 404 and error message when we try to delete a non-existent customer"in{customersIds.map{id=>Delete(s"$customerLink/$id")~>customerService~>check{response.statusshouldbeequalToNotFoundresponse.entityshouldnotbeequalTo(None)responseAs[Map[String, String]].get("error")===Some(s"Customer with id=$id does not exist")}}.find(!_.isSuccess)===None}}}