Test-Driven Development with Phoenix (self-published) (free)

Hi @tjdam, thanks for reading the book.

I may be head of myself here, but it seems like the type of workflow you use in the book with Wallaby and all would be the best fit for the type of apps I’m building?

I think I understand your question, but please correct me if I misunderstood it.

When I build APIs, I follow the same outside-in process that I follow in the book. The difference is that the outermost test is usually a controller test (or something equivalent). So you would not need Wallaby.

Wallaby’s purpose is to emulate browser interactions in our tests. Since you’re building APIs, that’s not needed. But the rest of the outside-in process should be the same.

Whatever the application’s domain, I usually try to find the test that best describes a feature for my customers. In APIs that tends to be an endpoint. So that’s where I define the outermost test, and then drive the implementation from there.

3 Likes

That answers my question, thank you!

And thank you again for the book :slight_smile:

1 Like

Some errata for you :slightly_smiling_face:

In chapter 8 (authentication) during refactoring you move import ChatterWeb.ConnTestHelpers into ChatterWeb.ConnCase but your example code highlights the wrong thing as being added (import Chatter.Factory)

At the end of chapter 9 (creating users) if you run the server and try to create a user you will get the following error despite all tests passing.

[debug] Processing with ChatterWeb.SessionController.create/2
  Parameters: %{"_csrf_token" => "AWNgFCMXGTFbTTM4MTwCITsMDzkwCCdQw-TpIomu4yZusykgM9PifMb2", "email" => "test@test.com", "password" => "[FILTERED]"}
  Pipelines: [:browser]
[debug] QUERY OK source="users" db=0.8ms queue=1.3ms idle=1252.9ms
SELECT u0."id", u0."email", u0."hashed_password", u0."session_secret", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."email" = $1) ["test@test.com"]
[info] CONNECTED TO ChatterWeb.UserSocket in 51µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"vsn" => "2.0.0"}
[info] Sent 500 in 203ms
[error] #PID<0.914.0> running ChatterWeb.Endpoint (connection #PID<0.903.0>, stream id 3) terminated
Server: localhost:4000 (http)
Request: POST /sessions
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function nil.id/0 is undefined
        nil.id()
        (doorman 0.6.2) lib/login/session.ex:12: Doorman.Login.Session.login/2
        (chatter 0.1.0) lib/chatter_web/controllers/session_controller.ex:11: ChatterWeb.SessionController.create/2
        (chatter 0.1.0) lib/chatter_web/controllers/session_controller.ex:1: ChatterWeb.SessionController.action/2
        (chatter 0.1.0) lib/chatter_web/controllers/session_controller.ex:1: ChatterWeb.SessionController.phoenix_controller_pipeline/2
        (phoenix 1.5.6) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
        (chatter 0.1.0) lib/chatter_web/endpoint.ex:1: ChatterWeb.Endpoint.plug_builder_call/2
        (chatter 0.1.0) lib/plug/debugger.ex:132: ChatterWeb.Endpoint."call (overridable 3)"/2
        (chatter 0.1.0) lib/chatter_web/endpoint.ex:1: ChatterWeb.Endpoint.call/2
        (phoenix 1.5.6) lib/phoenix/endpoint/cowboy2_handler.ex:65: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy 2.8.0) /home/matt/prog/chatter/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.8.0) /home/matt/prog/chatter/deps/cowboy/src/cowboy_stream_h.erl:300: :cowboy_stream_h.execute/3
        (cowboy 2.8.0) /home/matt/prog/chatter/deps/cowboy/src/cowboy_stream_h.erl:291: :cowboy_stream_h.request_process/3
        (stdlib 3.13.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

Thanks for letting me know about these. I’ll update the syntax highlighting for the first, and look into what might be causing the second.

Thank you for sharing your work.
Do you recommend this for new folks (with some programming experience) as well?

I do. If you are new to Elixir and Phoenix, this is a good place to start.

2 Likes

Yes I think this is a fantastic place to start. I wish this were available earlier :slight_smile:

2 Likes

Hi @triad,

Do you recommend this for new folks (with some programming experience) as well?

I think it depends. I don’t cover a lot of topics in depth because I assume prior knowledge of Elixir and Phoenix.

That being said, if you already have prior programming experience, and you’re comfortable looking for documentation on things not covered, it could be a good way to learn Phoenix. It seems like others here think so too, so it might be worth a shot.

If you give it a try, I’d love to hear your thoughts on it.

This is really awesome, thank you for posting! I only wish I had started my current project with this methodology, I think it would have massively improved my testing flow.

My current branch has almost 400 tests, and that’s not even the bare minimum i should have to fully cover the logic bearing code. It is as much of a nightmare as one could expect.

1 Like

I really enjoyed the accuracy in guiding the setup procedure! Thank you for this great help!

A little detail that some Mac users may face, which is related with the latest Mac OS versions is when getting the following error:

“chromedriver” cannot be opened because the developer cannot be verified.

I have addressed this issue by running:

xattr -d com.apple.quarantine <pathto/chromedriver>

I am not sure if it is safe in general, but I trusted that chromedriver in particular is not dangerous.

1 Like

I finally got a chance to look into this. I realized that in the authentication chapter, we never test the case when someone enters an incorrect email/password combination.

So the error you saw happens because we’re assuming that Doorman.authenticate(email, password) returns a user. But if the email or password are incorrect, it returns nil, so we get a call to nil.id` later.

Thanks for catching this. I’ll have to update the Authentication chapter to test that case.

1 Like

I also recently added a Troubleshooting chapter, mostly to deal with common ChromeDriver issues people have been running into. If anyone runs into ChromeDriver issues, give it a look.

1 Like

@mstibbard I just added a section in the Authentication chapter that addresses the issue you found. Thanks so much for pointing it out! :pray:

https://www.tddphoenix.com/authentication/#testing-invalid-authentication-credentials

2 Likes

Hey, thank you for taking the time to write this book!

I’ve got some experience programming but never tried true TDD, so after listening to the podcast episode I was curious about how strict one could get while doing TDD and decided to give your book a go.

I’m now at the point where ExMachina is introduced for the first time in the book, and would like to leave some (unsolicited obviously!) feedback about a little thing that bothers me in the few pages I’ve read so far: I guess I would appreciate at the start of each chapter/section a little summary of what the goal of the chapter is and how we’re going to go about it. I feel like I’m blindingly following the instructions like “copy this” and “create that file” and “add that dependency” without really understanding why, and I find that I frequently have to stop or skip a few paragraphs ahead to see where it is that these instructions will lead to and it is somewhat distracting to me.

Anyway, please don’t let any negative feedback bog you down- good on you for going all the way of creating a book to share your knowledge. Much appreciated!

2 Likes

OK. I’ve now gone through a bigger chunk of the book (adding authorization now) and I believe I understand the style better: essentially making a plan and executing it goes somewhat against the TDD style this book looks to impart (not sure if everyone practices TDD the same way so I’m reluctant to generalize), as the whole point is to “describe what you need to do” in a feature test, and let failing tests guide the implementation. I imagine having laid out a “master plan” of what will be attempted for each chapter would “spoil” this TDD approach and readers would be perhaps tempted to jump ahead and work on the code first instead of the tests first.

I guess this is me trying to say that I was wrong in my earlier assessment- perhaps what’s missing instead is a re-affirmation of trusting in the process at the start of each chapter to quell this feeling of unease some readers (like me) might have. Again, good job @germsvel!

PS: based on the talk in the podcast (and personal experience now :blush:) the editor setup is important for TDD to make the whole cycle faster, so by following these posts here (thank you @atomkirk & @robottokauf3- I hope I’ve tagged the right people) Elixir TDD in VSCode and Visual Studio Code Setup For Elixir Development I wanted to share my VSCode setup for running tests:

  • ctrl + t; f: run all tests in current file (useful to start the cycle)
  • ctrl + t; ctrl + f: run all failing tests (going with the flow this ends up running the latest test being worked on)
  • ctrl + t, t: run all tests (running this during the refactor step to make sure nothing’s missed)
3 Likes

Thanks for the suggestions @bottlenecked.

at the start of each chapter/section a little summary of what the goal of the chapter is and how we’re going to go about it. I feel like I’m blindingly following the instructions

I think this is great feedback. I don’t want the book to feel that way.

essentially making a plan and executing it goes somewhat against the TDD style this book looks to impart

I don’t think having a master plan goes against BDD/TDD. In fact, I think not considering design or approach is a misunderstanding of TDD for a lot of people. We can’t completely check out brains at the door and follow errors. We have to consider the design.

When I go through each chapter, I have a master plan in my head, but I imagine I’m just not communicating it enough. So, I think your original feedback is very helpful.

One question I had, how detailed a plan are you thinking of? I have something in my head when we talk about a master plan, but I wonder if its the same thing you’re thinking.

If you have a sense of what kind of master plan would be helpful, let me know. And thanks again for the feedback.

2 Likes

Hey, thanks for not taking this the wrong way :slight_smile:

I only mentioned planning may go against the TDD principles because sometimes to learn something new one has to unlearn some habits, and although having an attack plan is required in the ‘real world’, for the purposes of this book and in order to get the reader to trust in the process and not running off to implement things ‘the old way’ perhaps this style of writing makes more sense. I don’t know really. Happy we discuss this actually!

OK, as to the master plan I was thinking of having before each chapter: a few words on what we’re going to achieve and how. E.g. ‘to create chatrooms we’re going to need a page listing all chatrooms and another page with a form for creating a new chatroom. Also the chatroom page itself, where users will later be able to actually chat. After all is said and done, we’re going to end up with N controllers/views/templates and M schemas. We’re also going to introduce such-and-such library that will help us with this-and-that’.

Anyway, I hope you keep on enjoying the process of writing a book and not getting discouraged by randoms on the internets; I’ve already learned a lot and made me reconsider a few things about tests I’ve written in the past.

2 Likes

Why did we pick Doorman over phx.gen.auth?

Thanks 1M for making this tutorial, going to attack it this weekend

1 Like

I think a little bit of this in each chapter could be really helpful. Thanks for suggesting it.

Anyway, I hope you keep on enjoying the process of writing a book and not getting discouraged by randoms on the internets; I’ve already learned a lot and made me reconsider a few things about tests I’ve written in the past.

Glad to hear that! :tada: And not to worry. I’m not discouraged by randoms on the internet :smile:. I really do appreciate the feedback. I want the book to be valuable to people wanting to learn TDD & BDD.

Thanks again. If you have other thoughts, comments, or suggestions, let me know.

2 Likes

Why did we pick Doorman over phx.gen.auth?

That’s an excellent question. I picked Doorman because it was something simple at the time when I started writing the book (a couple of years ago). But changing that chapter to use phx.gen.auth (or something like that) is in my list of to-dos for v1.

If you’re interested in what else is on the roadmap for v1, I wrote a short list in TDD Phoenix Update (Nov 2020)

5 Likes