Top Banner
TRUE END-TO-END TESTING SCALA Orr Sella IN Scalapeño 2014
104

True End-to-End Testing in Scala

May 25, 2015

Download

Software

orrsella

Exploring what end-to-end tests are, why they are important for Scala apps, and how to write them. A talk given at Scalapeno 2014. Talk is accompanied by a code example: https://github.com/orrsella/scala-e2e-testing

Abstract:

It seems that by 2014 we have all come to the realization that testing is good, and today no reputable library/framework will be released without comprehensive tests. Whether you're developing using TDD or not, you're probably writing tests in some capacity. We have a plethora of testing libraries and tools that make writing tests in Scala extremely easy.

In this talk we will show how we can take our unit/integration tests a step further, and see how to test our applications end-to-end. Scala is a prime candidate for end-to-end testing, having all the right pieces in place: an easily extendable build tool (SBT), fluent DSL testing libraries (Specs2/ScalaTest), and the power/tooling of the JVM behind it.

We will see how to approach testing our application from the outside, and touch on concepts such as: the Test Harness, abstracting our SUT (System Under Test) by using test Drivers, using Simplicators for testing against external dependencies, and more. We will put everything together by using tools such as: SBT, Ansible and Vagrant.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: True End-to-End Testing in Scala

TRUEEND-TO-END

TESTING SCALA

Orr Sella

IN

Scalapeño 2014

Page 2: True End-to-End Testing in Scala

AGENDAWHAT ARE THEY WHY WE NEED THEM HOW TO WRITE THEM

END-TO-END TESTS

TEST HARNESS DRIVERS FAKES …

Page 4: True End-to-End Testing in Scala

BIG SUBJECT, WE CAN’T COVER EVERYTHING

Disclaimer

Page 5: True End-to-End Testing in Scala

WhySCALA & END-TO-END TESTS

1 THE APPS Typical Scala applications are ripe for end-to-end tests !

THE LANGUAGE & ECOSYSTEM Build tools, testing libraries and language features are a great fit

2

Page 6: True End-to-End Testing in Scala

SOME TERMINOLOGYFirst,

Page 7: True End-to-End Testing in Scala

The Bible: GOOSINTRO TO TDD !

TESTING TECHNIQUES !

LOTS OF CODE & EXAMPLES

Page 8: True End-to-End Testing in Scala

LEVELS OFTHETESTING KNOWN TO MAN

1 UNIT Do our objects do the right thing? !

INTEGRATION Does our code work against code we can’t change? !

END-TO-END Does the whole system work?

23

(not really)

Page 9: True End-to-End Testing in Scala

RIGHT BALANCEWHAT ISTHE

OF THE THREE?

Page 10: True End-to-End Testing in Scala

INVERSETHEICE CREAMCONE(or pyramid)

NUMBER OF TESTS

Page 11: True End-to-End Testing in Scala

INVERSETHEICE CREAMCONE(or pyramid)

END-TO-END

INTEGRATION

UNIT

NUMBER OF TESTS

Page 12: True End-to-End Testing in Scala

INVERSETHEICE CREAMCONE(or pyramid)

END-TO-END

INTEGRATION

UNIT

MANUAL TESTING (if you must)

NUMBER OF TESTS

Page 13: True End-to-End Testing in Scala

END-TO-ENDWHAT

ARETESTS?

Page 14: True End-to-End Testing in Scala

END-TO-END TESTSInteract with the system from the outside

Black Box

MOST IMPORTANTLY:

Exercising both the system and the process by which it’s built and deployed

Include interaction with external environment

Page 15: True End-to-End Testing in Scala

AUTOMATICAN IDEALBUILDWOULD

1 Compile and unit-test the code

Integrate and package the system

Perform a production-like deployment to a realistic environment

Exercise the system through its external access points

234

Page 16: True End-to-End Testing in Scala

AUTOMATICAN IDEALBUILDWOULD

1 Compile and unit-test the code

Integrate and package the system

Perform a production-like deployment to a realistic environment

Exercise the system through its external access points

234

Page 17: True End-to-End Testing in Scala

THIS SOUNDS LIKE A LOT OF WORK

Whoa,

Page 18: True End-to-End Testing in Scala

THIS SOUNDS LIKE A LOT OF WORK

Whoa,

IT IS.

Page 19: True End-to-End Testing in Scala

IS IT WORTH IT?But,

Page 20: True End-to-End Testing in Scala

IS IT WORTH IT?But,

YES. (I hope to convince you)

Page 21: True End-to-End Testing in Scala

END-TO-ENDWHY

ARETESTS SO IMPORTANT?

Page 22: True End-to-End Testing in Scala

REASONSHERE ARE3MAIN

(many more...)

Page 23: True End-to-End Testing in Scala

FLUSH OUTTHE UNKNOWNS

1

Requires asking (and answering) many awkward questions

Expose uncertainty early, including technical and organizational risks

Forces to understand how a system fits into the world

Identify all the integration (read: potential failure) points

Page 24: True End-to-End Testing in Scala

FLUSH OUTTHE UNKNOWNS

1

Requires asking (and answering) many awkward questions

Expose uncertainty early, including technical and organizational risks

Forces to understand how a system fits into the world

Identify all the integration (read: potential failure) points

EXAMPLE IN 3 SLIDES

Page 25: True End-to-End Testing in Scala

FEEDBACK &CONFIDENCE

2

Feedback that would otherwise only show in staging/production

Safety net of system-wide regression

Assurance that our chosen technology stack (“plumbing”) works as expected

Confidence to make “risky” system-wide changes (e.g: swap datastore, application server)

Page 26: True End-to-End Testing in Scala

FORCEDAUTOMATION

3

Everything must be automated so it can be tested

Crucial for deployment which is error-prone

Automatic Build-Deploy-Test cycle means we can ship frequently

Page 27: True End-to-End Testing in Scala

TIME FOR A CONCRETEEXAMPLE:

Page 28: True End-to-End Testing in Scala

TIME FOR A CONCRETEEXAMPLE:Memento

Page 30: True End-to-End Testing in Scala

NOTE-TAKING WEB SERVICE

Memento?

Page 31: True End-to-End Testing in Scala

NOTE-TAKING WEB SERVICE

Memento?

STORE, RETRIEVE, SEARCH, TRANSLATE

Page 32: True End-to-End Testing in Scala

NOTE-TAKING WEB SERVICE

Memento?

STORE, RETRIEVE, SEARCH, TRANSLATE“MICROSERVICE”

Page 33: True End-to-End Testing in Scala

NOTE-TAKING WEB SERVICE

Memento?

STORE, RETRIEVE, SEARCH, TRANSLATE“MICROSERVICE”REST API

Page 34: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEX

Page 35: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEXSCALA!..

Page 36: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEXSCALA!..

PRETTY STRAIGHTFORWARD

Page 37: True End-to-End Testing in Scala

WHAT CAN POSSIBLY GO WRONG & BE MISCONFIGURED?

Ok. So.

i.e. integration (failure) points

Page 38: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

PORT 80 /ETC/HAPROXY/HAPROXY.CFG

LIST OF REVERSE PROXIES:PORTS

/ETC/DEFAULT/HAPROXY

Page 39: True End-to-End Testing in Scala

REVERSE PROXY NGINX

APP SERVER FINATRA

PROXY PORT 7770

/ETC/NGINX/NGINX.CONF

INIT.D SCRIPT

/ETC/NGINX/SITES-AVAILABLE

JAVA

APPLICATION.CONF LOGBACK.XML

DEPLOY NEW VERSION

Page 40: True End-to-End Testing in Scala

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEX

PORT 9300MAPPING

URLAPI KEY

/ETC/ELSATICSEARCH.YML

JAVA

Page 41: True End-to-End Testing in Scala

(just a partial list…)

Page 42: True End-to-End Testing in Scala

SO A LOT CAN GO WRONG

Ok,

(read: will)

Page 43: True End-to-End Testing in Scala

SO A LOT CAN GO WRONG

Ok,

(read: will)

WE WANT OUR END-TO-END TESTS

TO EXERCISE ALL OF THIS!

Page 44: True End-to-End Testing in Scala

HOW DO WE TEST ALL OF THISEND-TO-END?

Page 45: True End-to-End Testing in Scala

HOW DO WE TEST ALL OF THISEND-TO-END?here is one approach…

Page 46: True End-to-End Testing in Scala

HOW DO WE TEST ALL OF THISEND-TO-END?here is one approach…

THERE ARE OTHERS

Page 47: True End-to-End Testing in Scala

HOW DO WE TEST ALL OF THISEND-TO-END?here is one approach…

THERE ARE OTHERS

BUT THIS IS THE BEST, OBVIOUSLY

Page 48: True End-to-End Testing in Scala

REQUIRED FORSTEPSEND-TO-END TESTS

1

BUILD & PACKAGE Package the app for production deployment

2

3

0VIRTUAL MACHINE Fire-up a local VM instance with production-like env

CONFIGURATION & DEPLOYMENT Run production config and deployment code, having our entire system on one box

RUN TESTS Execute end-to-end tests against the VM

Page 49: True End-to-End Testing in Scala

ORCHESTRATEHOW DO

WEALL OF THIS?

Page 50: True End-to-End Testing in Scala

THE TEST HARNESS

Enter:

EXTENSION OF THE BUILD Runs all the necessary setup/teardown actions and the tests themselves !

STANDARD/SIMPLE/SCALA BUILD TOOL Build and package our app (sbt-native-packager) Fire-up the virtual machine (Vagrant) Run configuration and deployment scripts (Ansible)

(aka sbt)

Page 51: True End-to-End Testing in Scala

VAGRANTMeet

PORTABLE DEV ENVIRONMENTS Lightweight, reproducible and programmable !

VIRTUALIZATION Wrapper around VirtualBox, VMWare, more !

SIMPLE & QUICK Vagrantfile => `$ vagrant up`

Page 52: True End-to-End Testing in Scala

2 CONFIGURATION & DEPLOYMENT Run production config and deployment code, having our entire system on one box

STEPS REQUIRED FORTHEEND-TO-END TESTS

BUILD & PACKAGE Package the app for production-like deployment

3

0

RUN TESTS Execute end-to-end tests against the VM

1 VIRTUAL MACHINE Fire-up a local VM instance with production-like env

Page 53: True End-to-End Testing in Scala

Test Harness:MANAGING VAGRANT

SBT

// project/Vagrant.scala!object Vagrant {! private lazy val vagrant = settingKey[Vagrant]("vagrant")! lazy val settings = Seq( test in EndToEndTest <<= (test in EndToEndTest).dependsOn(publishLocal), testOptions in EndToEndTest += Tests.Setup(() => vagrant.value.setup()), testOptions in EndToEndTest += Tests.Cleanup(() => vagrant.value.cleanup()) )}

Page 54: True End-to-End Testing in Scala

Test Harness:MANAGING VAGRANT

SBT

// project/Vagrant.scala!object Vagrant {! private lazy val vagrant = settingKey[Vagrant]("vagrant")! lazy val settings = Seq( test in EndToEndTest <<= (test in EndToEndTest).dependsOn(publishLocal), testOptions in EndToEndTest += Tests.Setup(() => vagrant.value.setup()), testOptions in EndToEndTest += Tests.Cleanup(() => vagrant.value.cleanup()) )}

TEARDOWN HOOK

SETUP HOOK

Page 55: True End-to-End Testing in Scala

Test Harness:MANAGING VAGRANT

SBTclass Vagrant(vagrantFile: File) {! // cli method wrappers private def up() = Process("vagrant" :: "up" :: Nil, dir)! private def provision() = Process("vagrant" :: "provision" :: Nil, dir)!! def setup(): Unit = { prevStatus = status() prevStatus match { case Running => provision() case Saved => up(); provision() case NotCreated => up() case Unknown => up() } }! def cleanup(): Unit = if (prevStatus != Running) suspend()}

Page 56: True End-to-End Testing in Scala

Test Harness:MANAGING VAGRANT

SBTclass Vagrant(vagrantFile: File) {! // cli method wrappers private def up() = Process("vagrant" :: "up" :: Nil, dir)! private def provision() = Process("vagrant" :: "provision" :: Nil, dir)!! def setup(): Unit = { prevStatus = status() prevStatus match { case Running => provision() case Saved => up(); provision() case NotCreated => up() case Unknown => up() } }! def cleanup(): Unit = if (prevStatus != Running) suspend()}

Page 57: True End-to-End Testing in Scala

Test Harness:MANAGING VAGRANT

SBTclass Vagrant(vagrantFile: File) {! // cli method wrappers private def up() = Process("vagrant" :: "up" :: Nil, dir)! private def provision() = Process("vagrant" :: "provision" :: Nil, dir)!! def setup(): Unit = { prevStatus = status() prevStatus match { case Running => provision() case Saved => up(); provision() case NotCreated => up() case Unknown => up() } }! def cleanup(): Unit = if (prevStatus != Running) suspend()}

Tip: VAGRANT STATE “JUGGLING”, SAVES A LOT

OF TIME!

Page 58: True End-to-End Testing in Scala

STEPS REQUIRED FORTHEEND-TO-END TESTS

1

BUILD & PACKAGE Package the app for production-like deployment

3

0VIRTUAL MACHINE Fire-up a local VM instance with production-like env

RUN TESTS Execute end-to-end tests against the VM

2 CONFIGURATION & DEPLOYMENT Run production config and deployment code, having our entire system on one box

Page 59: True End-to-End Testing in Scala

DEPLOYMENTTHESCRIPTS

CONFIGURATION MANAGEMENT Ensure that our system is configured properly (OS, settings, packages, file system, etc.) !

DEPLOY THE APP Upgrade to the latest version just compiled and packaged, as would be done in production !

APPLICATION CONFIG Configure the application itself the same way it would in production (but with test values)

(Provision in Vagrant parlance)

Page 60: True End-to-End Testing in Scala

ANSIBLE BUT ANY CM TOOL WILL DO

Specifically,

(Chef, Puppet, Shell scripts)

Page 61: True End-to-End Testing in Scala

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|! config.vm.box = "ubuntu/trusty64" config.vm.network "forwarded_port", guest: 7770, host: 7769 ...! config.vm.provision "ansible" do |ansible| ansible.playbook = "ansible/site.yml" ansible.inventory_path = "ansible/inventories/vagrant" end!end

Provision:VAGRANTFILE

Page 62: True End-to-End Testing in Scala

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|! config.vm.box = "ubuntu/trusty64" config.vm.network "forwarded_port", guest: 7770, host: 7769 ...! config.vm.provision "ansible" do |ansible| ansible.playbook = "ansible/site.yml" ansible.inventory_path = "ansible/inventories/vagrant" end!end

Provision:VAGRANTFILE

Page 63: True End-to-End Testing in Scala

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|! config.vm.box = "ubuntu/trusty64" config.vm.network "forwarded_port", guest: 7770, host: 7769 ...! config.vm.provision "ansible" do |ansible| ansible.playbook = "ansible/site.yml" ansible.inventory_path = "ansible/inventories/vagrant" end!end

Provision:VAGRANTFILE

VAGRANT INVOKES THE PROVISIONER

Page 64: True End-to-End Testing in Scala

CONFIGURATION & DEPLOYMENT Run production config and deployment code, having our entire system on one box

BUILD & PACKAGE Package the app for production-like deployment

VIRTUAL MACHINE Fire-up a local VM instance with production-like env

STEPS REQUIRED FORTHEEND-TO-END TESTS

3 RUN TESTS Execute end-to-end tests against the VM

2

10

Page 65: True End-to-End Testing in Scala

CONFIGURATION & DEPLOYMENT Run production config and deployment code, having our entire system on one box

BUILD & PACKAGE Package the app for production-like deployment

VIRTUAL MACHINE Fire-up a local VM instance with production-like env

STEPS REQUIRED FORTHEEND-TO-END TESTS

3 RUN TESTS Execute end-to-end tests against the VM

2

10

CHECK OUT CODE EXAMPLE

Page 66: True End-to-End Testing in Scala

2 CONFIGURATION & DEPLOYMENT Run production config and deployment code, having our entire system on one box

STEPS REQUIRED FORTHEEND-TO-END TESTS

1

BUILD & PACKAGE Package the app for production-like deployment0VIRTUAL MACHINE Fire-up a local VM instance with production-like env

3 RUN TESTS Execute end-to-end tests against the VM

Page 67: True End-to-End Testing in Scala

WRITING THEFinally,

TESTS THEMSELVES

Page 68: True End-to-End Testing in Scala

GUIDELINESSOMEE2E TESTSFOR effective

TEST SPARINGLY End-to-End tests are slow; test few “sunny-day” scenarios !

ABSTRACTION Tests validate features and business logic, not implementation details !

AVOID COUPLING Tests should exercise the system from the outside without reaching for its guts

Page 69: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

Page 70: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

USING SPECS2

Page 71: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

Page 72: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

TESTING THE SYSTEM BY

USING ITSELF

Page 73: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

TEST IS DECOUPLED FROM THE CONTROLLER API ITSELF BY USING DRIVERS

Page 74: True End-to-End Testing in Scala

Interact with the SUT directly instead of the test

Coupled to the API/protocol

Run same test with different drivers

Drivers:ABSTRACTING THE SUT

Page 75: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

Page 76: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

ONLY THE DRIVER INTIMATELY KNOWS THE CONTROLLER

Page 77: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

ONLY THE DRIVER INTIMATELY KNOWS THE CONTROLLER

Page 78: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

ONLY THE DRIVER INTIMATELY KNOWS THE CONTROLLER

Page 79: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

ONLY THE DRIVER INTIMATELY KNOWS THE CONTROLLER

class AddNoteResponse(response: Response)

extends BaseResponse(response)

with JsonResponse {! lazy val noteId = (json \ "noteId").

extract[String]

}

Page 80: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

ONLY THE DRIVER INTIMATELY KNOWS THE CONTROLLER

class AddNoteResponse(response: Response)

extends BaseResponse(response)

with JsonResponse {! lazy val noteId = (json \ "noteId").

extract[String]

}

ONLY THE DRIVER KNOWS THE

RESPONSE IS JSON

Page 81: True End-to-End Testing in Scala

Drivers:ABSTRACTING THE SUT

trait NotesControllerDriver {! def anAddNoteRequest = AddNoteRequest()! case class AddNoteRequest(text: String = "Lorem ipsum") extends Request[AddNoteResponse] {! val method = HttpMethod.POST val path = "/notes" val params: Map[String, String] = Map() val headers: Map[String, String] = Map() val body: Option[String] = Some("{ \"text\": \"" + text + "\" }")! def withText(text: String) = copy(text = text) } ...}

ONLY THE DRIVER INTIMATELY KNOWS THE CONTROLLER

class AddNoteResponse(response: Response)

extends BaseResponse(response)

with JsonResponse {! lazy val noteId = (json \ "noteId").

extract[String]

}

ONLY THE DRIVER KNOWS THE

RESPONSE IS JSONOR THE FIELD

NAME

Page 82: True End-to-End Testing in Scala

(back to the test…)

Page 83: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

Page 84: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

Page 85: True End-to-End Testing in Scala

Matchers:ABSTRACTING THE SUT

The other side of the Driver, i.e: the response

Help decoupling from the protocol, make the test be about features

Make test more readable

Page 86: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

Page 87: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

trait ResponseMatchers extends Matchers {

private implicit def intToIntMatcher(t: Int): Matcher[Int] = beEqualTo(t)

! def beOk = haveStatus(200) def beBadRequest = haveStatus(400)

def beNotFound = haveStatus(404)

! def haveStatus(status: Matcher[Int])

: Matcher[Response] =

((_: Response).status) ^^ status

}

Page 88: True End-to-End Testing in Scala

Memento:NOTES CONTROLLERTEST

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! "Notes controller" should { "add a note and then successfully get it" in {! val addResp = anAddNoteRequest.withText("Hello world!").execute() addResp must beOk addResp.noteId must not beEmpty! val getResp = aGetNoteRequest.withId(addResp.noteId).execute() getResp must beOk getResp.text must_== "Hello world!" } }}

Page 89: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEX

Page 90: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEX

Page 91: True End-to-End Testing in Scala

TESTINGEXTERNAL SERVICES

Page 92: True End-to-End Testing in Scala

As always,LIFE IS ABOUT COMPROMISES AND SETTING BOUNDARIES

Page 93: True End-to-End Testing in Scala

As always,LIFE IS ABOUT COMPROMISES AND SETTING BOUNDARIES

=> USE FAKES

Page 94: True End-to-End Testing in Scala

FAKES FOR

Avoid making external network calls in tests

Create Fakes based on available documentation

Fakes should have minimal implementation to only support testing

Test the Fake against the real service in a contract test that’s manually run

USEEXTERNAL SERVICES

NOT EASILY AVAILABLE(Sometimes called Simplicators)

Page 95: True End-to-End Testing in Scala

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! private val port = 9921 private val server = new FakeYandexTranslateServer(port)! step { server.start() }! "Notes controller" should { "add a note and then get it translated" in { ... val translated = aTranslateRequest.withId(noteId).withLang("es").execute() translated must beOk translated.text must_== "Buenos días" } }! step { server.stop() }}

Page 96: True End-to-End Testing in Scala

class NotesControllerEndToEndTest extends Specification with NotesControllerDriver with ResponseMatchers {! private val port = 9921 private val server = new FakeYandexTranslateServer(port)! step { server.start() }! "Notes controller" should { "add a note and then get it translated" in { ... val translated = aTranslateRequest.withId(noteId).withLang("es").execute() translated must beOk translated.text must_== "Buenos días" } }! step { server.stop() }}

class FakeYandexTranslateServer(port: Int) extends SimpleHttpServer(port) {

! private val json = """{ |"code": 200, |"lang": "en-es", |"text": ["Buenos días"]

|} """.stripMargin! override protected def onSimpleReque

st(request: HttpRequest): String = json

}

Page 97: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEX

Page 98: True End-to-End Testing in Scala

LOAD BALANCER HAPROXY

REVERSE PROXY NGINX

APP SERVER FINATRA

DATASTORE ELASTICSEARCH

TRANSLATION SERVICE YANDEX

Page 99: True End-to-End Testing in Scala

WHAT YOU SHOULDDO NEXT

Page 100: True End-to-End Testing in Scala

CONTINUE FROM HERE

How to

CHECK OUT THE EXAMPLE github.com/orrsella/scala-e2e-testing !

END-TO-END TEST NEW PROJECTS It is easier to get started with e2e tests on a new project, integrating into an existing one is harder !

READ Growing Object-Oriented Software Guided by Tests

Page 101: True End-to-End Testing in Scala

CONTINUE FROM HERE

How to

CHECK OUT THE EXAMPLE github.com/orrsella/scala-e2e-testing !

END-TO-END TEST NEW PROJECTS It is easier to get started with e2e tests on a new project, integrating into an existing one is harder !

READ Growing Object-Oriented Software Guided by Tests

Page 102: True End-to-End Testing in Scala

THANK YOU

Page 103: True End-to-End Testing in Scala
Page 104: True End-to-End Testing in Scala