{"id":16455281,"url":"https://github.com/cchantep/tdd-tutorial","last_synced_at":"2025-10-27T23:05:25.647Z","repository":{"id":66753113,"uuid":"142473832","full_name":"cchantep/tdd-tutorial","owner":"cchantep","description":"A TDD tutorial based on ReactiveMongo and particularly the PullRequest reactivemongo/reactivemongo#750","archived":false,"fork":false,"pushed_at":"2018-07-27T13:06:52.000Z","size":23,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-26T22:44:05.189Z","etag":null,"topics":["reactivemongo","scala","specs2","tdd"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cchantep.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-26T17:38:35.000Z","updated_at":"2020-03-27T13:27:49.000Z","dependencies_parsed_at":"2023-02-24T16:15:18.243Z","dependency_job_id":null,"html_url":"https://github.com/cchantep/tdd-tutorial","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/cchantep/tdd-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cchantep%2Ftdd-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cchantep%2Ftdd-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cchantep%2Ftdd-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cchantep%2Ftdd-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cchantep","download_url":"https://codeload.github.com/cchantep/tdd-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cchantep%2Ftdd-tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281355386,"owners_count":26486904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-27T02:00:05.855Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["reactivemongo","scala","specs2","tdd"],"created_at":"2024-10-11T10:21:48.601Z","updated_at":"2025-10-27T23:05:25.634Z","avatar_url":"https://github.com/cchantep.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\ntitle: TDD tutorial\ndate: \\today\nfontsize: 11pt\nmonofont: Menlo\nmainfont: Avenir\nheader-includes:\n- \\usepackage{pandoc-solarized}\n- \\input{beamer-includes}\n---\n\n## TDD tutorial\n\nA TDD tutorial based on ReactiveMongo and particularly the PullRequest [#750](https://github.com/ReactiveMongo/ReactiveMongo/pull/750).\n\n## Introduction\n\nThe goal is to share a [TDD](https://en.wikipedia.org/wiki/Test_driven_development) example, based on [ReactiveMongo code](http://github.com/reactivemongo/reactivemongo).\n\nThe Pull Request [#750](https://github.com/ReactiveMongo/ReactiveMongo/pull/750), which aims to implement a support for [DNS seed list](https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.rst#initial-dns-seedlist-discovery), will be used as subject.\n\n*Estimated time: 1h 30min (reading time 10min)*\n\n\u003e Keywords: [tdd](https://en.wikipedia.org/wiki/Test_driven_development), [scala](https://www.scala-lang.org/), [specs2](https://etorreborre.github.io/specs2/)\n\n## Requirements\n\n**Dev tools:**\n\n- [Git client](https://git-scm.com/)\n- JDK 1.8+\n- [SBT](http://scala-sbt.org/)\n\n**Knowledge:**\n\n- *required:* [Scala 2.12+](https://www.scala-lang.org/)\n- *optional:* [specs2](https://etorreborre.github.io/specs2) (test framework)\n\n## Start the tutorial\n\nYou first need to git-clone this tutorial:\n\n```bash\ngit clone git@github.com:cchantep/tdd-tutorial.git \\\n  --branch master --single-branch\n```\n\nThen the TDD approach is described step by step thereafter.\n\n## Phase 1 - Setup dnsjava\n\nWe will be using the library [*dnsjava*](http://www.dnsjava.org/), so at first step you need to add the corresponding [dependency (version 2.1.8)](http://search.maven.org/#artifactdetails%7Cdnsjava%7Cdnsjava%7C2.1.8%7Cjar) in the [`build.sbt`](https://github.com/cchantep/tdd-tutorial/blob/master/build.sbt) (make sure to reload the build in SBT).\n\nOnce that's ok, we will starting testing right now, by checking how to use *dnsjava* to resolve SRV records.\n\nAt this point you can start the REPL using `sbt console` (or just type `console` if SBT is already launched).\n\n## Phase 1 - Read documentation \u0026 first test\n\nWhen the SBT Scala REPL is started, it's time to read the [documentation](http://www.dnsjava.org/dnsjava-current/doc/) to figure out how to perform a SRV lookup, and test it for `_imaps._tcp.gmail.com` in the REPL.\n\nIt should print an expected result as below:\n\n```\nArray[org.xbill.DNS.Record] = Array(\n  _imaps._tcp.gmail.com.\t21599\tIN\tSRV\t\\\n  5 0 993 imap.gmail.com.)\n```\n\n*Time to code \u0026 test: 10min (solution thereafter)*\n\n## Phase 1 - SRV Lookup in the REPL\n\nYou can paste the following snippet to test SRV resolution.\n\n```scala\nimport org.xbill.DNS._\n\nnew Lookup(\"_imaps._tcp.gmail.com\", Type.SRV).run()\n```\n\n## Phase 1 - Refactor as a function\n\nStill in the REPL, you can now implement the following function using the tested lookup code, to return only the [target name](http://www.dnsjava.org/dnsjava-current/doc/org/xbill/DNS/SRVRecord.html#getTarget()) of the resolved SRV record.\n\n```scala\nimport org.xbill.DNS.Name\ndef records(name: String): Array[Name] = ???\n// lookup for SRV record _imaps._tcp.\u003cname\u003e\n```\n\nThis function can immediately by tested in the REPL.\n\n```scala\nrecords(\"gmail.com\")\n// Expected result: ... Array(imap.gmail.com.)\n```\n\n*Time to code \u0026 test: 5min (solution thereafter)*\n\n## Phase 1 - REPL records function\n\nThe `records` function can be declared as below in the REPL.\n\n```scala\ndef records(name: String): Array[Name] =\n  new Lookup(s\"_imaps._tcp.${name}\", Type.SRV).\n  run().map(_.getAdditionalName)\n```\n\n## Phase 1 - Timeout resolution\n\nIn order to configure the timeout for the SRV resolution, a custom `Resolver` can be used as follows (you can test it in the REPL).\n\n```scala\nimport org.xbill.DNS._\n\ndef customResolver: Resolver = {\n  val r = Lookup.getDefaultResolver\n  r.setTimeout(5/*seconds*/)\n  r\n}\n\nval lkup = new Lookup(\"_imaps._tcp.gmail.com\", Type.SRV)\nlkup.setResolver(customResolver)\nlkup.run()\n```\n\n## Phase 1 - Contracts as signature \u0026 tests\n\nAfter these interactive tests in the REPL, according the TDD approach we will go on with the TDD approach, first writing a test for the SRV resolution.\n\nThe expected contracts for such resolution are written as,\n\n- the \"not implemented\" [function `srvRecords`](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala#L42),\n- the [corresponding test](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala#L52).\n\n## Phase 1 - Start with failing test\n\nThis test can be executed in SBT:\n\n```bash\ntestOnly -- include srvRecords\n```\n\nAt this point, without `srvRecords` implementation, the test is expected to fail.\n\n```\n[info] Utilities\n[error]   x resolve SRV record for _imaps._tcp at ..\n[error]    an implementation is missing (..)\n[info] Total for specification Utilities\n[info] Finished in 185 ms\n[info] 1 example, 1 failure, 0 error\n```\n\n## Phase 1 - Implement srvRecords to match acceptance\n\nNow it's time to make the test happy, by implementing `srvRecords` so it matches its contract.\n\nTo do so, you can edit the function in [UtilSpec](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala).\n\nThe SBT task must be executed continuously until the function no longer raises any error.\n\n```bash\ntestOnly UtilSpec -- include srvRecords\n```\n\n*Time to code \u0026 test: 5min (solution thereafter)*\n\n## Phase 1 - Solution \u0026 expected result\n\n*See the [online solution](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase1/src/test/scala/UtilSpec.scala#L42)*\n\nExpected test results:\n\n```\n[info] Utilities\n[info]   + resolve SRV record for _imaps._tcp at ...\n[info] Total for specification Utilities\n[info] Finished in 195 ms\n[info] 1 example, 0 failure, 0 error\n[info] Passed: Total 1, Failed 0, Errors 0, Passed 1\n```\n\n## Phase 1 - Commit\n\nAt this step, it's time to commit the changes applied on `build.sbt` and `UtilSpec.scala`.\n\n```bash\ngit commit -a -m \"Phase #1\"\n```\n\n\u003e The code expected after this phase can be checked with the online tag [`tdd/phase1`](https://github.com/cchantep/tdd-tutorial/tree/tdd/phase1).\n\n## Phase 2 - List TXT records\n\nWe know how to resolve SRV records, to implements [DNS seed list](https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.rst#initial-dns-seedlist-discovery), TXT records are also required.\n\nYou can first test it for domain gmail.com in the REPL: `sbt console`\n\nConsidering only the RDATA for each record, the expected result is `Array(\"v=spf1 redirect=_spf.google.com\")`\n\n*Time to code \u0026 test: 10min (solution thereafter)*\n\n## Phase 2 - TXT lookup in REPL\n\nThe TXT records for gmail.com can be checked as below.\n\n```scala\nimport org.xbill.DNS._\n\nnew Lookup(\"gmail.com\", Type.TXT).\n  run().map(_.rdataToString)\n// =\u003e Array(\"v=spf1 redirect=_spf.google.com\")\n```\n\n## Phase 2 - Contracts as signature \u0026 tests\n\nAfter these REPL tests, we will save it in `UtilSpec.scala` with the \"not implemented\" [function `txtRecords`](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala#L47).\n\nAccording the function signature, you have to write the [test in `UtilSpec`](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala#L56) to check that TXT resolution for gmail.com returns `v=spf1 redirect=_spf.google.com`.\n\n```scala\n\"resolve TXT record for gmail.com\" in {\n  todo /* List(\"v=spf1 redirect=_spf.google.com\") */\n}\n```\n\n*Time to code test: 1min*\n\n## Phase 2 - Start with failing test\n\nAs soon as the test for `txtRecords` is written, it can be used in SBT.\n\n```bash\ntestOnly\n```\n\nFor now, this test is expected to fail (without the function implementation).\n\n## Phase 2 - Implement txtRecords to match acceptance\n\nFrom there, you can implement the `txtRecords` function in [UtilSpec](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala).\n\nUntil the corresponding test is successful, the SBT task can be executed continuously.\n\n```bash\ntestOnly UtilSpec\n```\n\n*Time to code \u0026 test: 5min (solution thereafter)*\n\n## Phase 2 - Solution \u0026 expected result\n\n*See the [online solution](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase2/src/test/scala/UtilSpec.scala#L72)*\n\nExpected test results:\n\n```\n[info] Utilities\n...\n[info] DNS resolver should\n[info]   + resolve SRV record for _imaps._tcp at ...\n[info]   + resolve TXT record for gmail.com\n[info] Total for specification Utilities\n[info] Finished in 331 ms\n[info] 4 examples, 0 failure, 0 error\n[info] Passed: Total 4, Failed 0, Errors 0, Passed 4\n```\n\n## Phase 2 - Commit\n\nAt this step, it's time to commit the changes applied on `UtilSpec.scala`.\n\n```bash\ngit commit -a -m \"Phase #2\"\n```\n\n\u003e The code expected after this phase can be checked with the online tag [`tdd/phase2`](https://github.com/cchantep/tdd-tutorial/tree/tdd/phase2).\n\n## Phase 3 - Refactor srvRecords function\n\nAt this step, the function `srvRecords` can be moved in the package object [`reactivemongo.util`](https://github.com/cchantep/tdd-tutorial/blob/master/src/main/scala/util/package.scala).\n\nWhile moving this function, you should also take the opportunity to add it the following parameter.\n\n```scala\ntimeout: FiniteDuration\n```\n\n*Time to code: 5min (solution thereafter)*\n\n## Phase 3 - Document srvRecords function\n\nIn order to finalize the `srvRecords` function, you can document it, with a Scaladoc as below.\n\n```scala\n/**\n * @param name the DNS name (e.g. `my.mongodb.com`)\n * @param timeout the resolution timeout (default: 5s)\n * @param srvPrefix the SRV prefix (e.g. `_mongodb._tcp`)\n */\n```\n\n*Time to document: 1min (solution thereafter)*\n\n## Phase 3 - Refactored svrRecords \u0026 Scaladoc\n\n*See the [refactoring online](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase3/src/main/scala/util/package.scala#L55)*\n\nAccording this refactoring, the tests in [`UtilSpec.scala`](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala) need to be updated.\n\n*Time to update tests: 1min (solution thereafter)*\n\n## Phase 3 - Test refactoring of srvRecords \n\n*See the [updated tests](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase3/src/test/scala/UtilSpec.scala#L94)*\n\nOnce done it can be used from SBT to make sure no regression is introduced:\n\n```bash\ntestOnly UtilSpec\n```\n\n## Phase 3 - Commit\n\nAt this step, it's time to commit the changes applied on `package.scala` and `UtilSpec.scala`.\n\n```bash\ngit commit -a -m \"Phase #3\"\n```\n\n\u003e The code expected after this phase can be checked with the online tag [`tdd/phase3`](https://github.com/cchantep/tdd-tutorial/tree/tdd/phase3).\n\n## Phase 4 - Refactor txtRecords function\n\nAs for `srvRecords`, the function `txtRecords` can also be moved in the package object [`reactivemongo.util`](https://github.com/cchantep/tdd-tutorial/blob/master/src/main/scala/util/package.scala).\n\nWhile moving this function, you should also take the opportunity to add it the following parameter.\n\n```scala\ntimeout: FiniteDuration\n```\n\n*Time to code: 5min (solution thereafter)*\n\n## Phase 4 - Document srvRecords function\n\nIn order to finalize the `srvRecords` function, you can document it, with a Scaladoc as follows.\n\n```scala\n/**\n * @param name the DNS name (e.g. `my.mongodb.com`)\n * @param timeout the resolution timeout (default: 5s)\n */\n```\n\n*Time to document: 1min (solution thereafter)*\n\n## Phase 4 - Refactored txtRecords \u0026 Scaladoc\n\n*See the [refactoring online](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase4/src/main/scala/util/package.scala#L98)*\n\nAccording this refactoring, the tests in [`UtilSpec.scala`](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala) need to be updated.\n\n*Time to update tests: 1min (solution thereafter)*\n\n## Phase 4 - Test the refactoring\n\n*See the [updated tests](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase4/src/test/scala/UtilSpec.scala#L39)*\n\nOnce done it can be used from SBT to make sure no regression is introduced:\n\n```bash\ntestOnly UtilSpec\n```\n\n## Phase 4 - Commit\n\nAt this step, it's time to commit the changes applied on `package.scala` and `UtilSpec.scala`.\n\n```bash\ngit commit -a -m \"Phase #4\"\n```\n\n\u003e The code expected after this phase can be checked with the online tag [`tdd/phase4`](https://github.com/cchantep/tdd-tutorial/tree/tdd/phase4).\n\n## Phase 5 - Refactor srvRecords \u0026 txtRecords\n\nYou can refactor both `srvRecords` and `txtRecords`,\nto return `Future` (for composition \u0026 error handling).\n\n*Time to code: 15min (solution thereafter)*\n\n## Phase 5 - Refactoring \u0026 Scaladoc\n\n*See the [refactoring online](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase5/src/main/scala/util/package.scala#L56)*\n\nAccording this refactoring, the tests in [`UtilSpec.scala`](https://github.com/cchantep/tdd-tutorial/blob/master/src/test/scala/UtilSpec.scala) need to be updated.\n\n(See [specs2 documentation](https://etorreborre.github.io/specs2/guide/SPECS2-4.2.0/org.specs2.guide.Matchers.html) about testing `Future`)\n\n*Time to update tests: 5min (solution thereafter)*\n\n## Phase 5 - Test the refactoring\n\n*See the [updated tests](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase5/src/test/scala/UtilSpec.scala#L34)*\n\nOnce done it can be used from SBT to make sure no regression is introduced:\n\n```bash\ntestOnly UtilSpec\n```\n\n## Phase 5 - Commit\n\nAt this step, it's time to commit the changes applied on `package.scala` and `UtilSpec.scala`.\n\n```bash\ngit commit -a -m \"Phase #5\"\n```\n\n\u003e The code expected after this phase can be checked with the online tag [`tdd/phase5`](https://github.com/cchantep/tdd-tutorial/tree/tdd/phase5).\n\n## Phase 6 - Refactor txtRecords with ListSet\n\nAs the TXT resolution must only consider the distinct records, the function `txtRecords` can be refactored to use [`ListSet`](https://www.scala-lang.org/api/2.12.6/scala/collection/immutable/ListSet.html) rather than `List`.\n\n*Time to code: 5min (solution thereafter)*\n\n## Phase 6 - Refactored txtRecords\n\n*See the [online solution](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase6/src/main/scala/util/package.scala#L138)*\n\nAfter this refactoring, the tests must be failing when using `testOnly` in SBT.\nSo `UtilSpec.scala` need to be updated accordingly.\n\n*Time to update tests: 1min (solution thereafter)*\n\n## Phase 6 - Tests \u0026 commit\n\n*See the [updated tests](https://github.com/cchantep/tdd-tutorial/blob/tdd/phase6/src/test/scala/UtilSpec.scala#L48)*\n\nAt this step, it's time to commit the changes applied on `package.scala` and `UtilSpec.scala`.\n\n```bash\ngit commit -a -m \"Phase #6\"\n```\n\n\u003e The code expected after this phase can be checked with the online tag [`tdd/phase6`](https://github.com/cchantep/tdd-tutorial/tree/tdd/phase6).\n\n## Complete solution\n\nYou can have a look at a complete solution for all this tutorial in the [`solution` branch](https://github.com/cchantep/tdd-tutorial/compare/solution#commits_bucket).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcchantep%2Ftdd-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcchantep%2Ftdd-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcchantep%2Ftdd-tutorial/lists"}