This separation has a lot of benefits. One of those is that it’s easier to determine where code should live. Is it related to the authoritative source of saving or providing information, or of implementing business rules or validation? When yes then the backend provides a cozy fit. Or, maybe it’s related to the user experience and how users interact with an application’s UI? When this is true then it will find itself right at home in the frontend.
The frontend and backend separation is not a panacea, but at one level it helps encourage of developers to do the right thing. We can spend less time wrestling with where code should live at the level of frontend or backend because we have a simple to set of heuristics that make it clear: frontend with this and backend with that.
This benefit extends itself to testing as well, unit testing specifically. Narrowly focused code is easier to write, maintain, and test. Ask any developer who has experience writing code and tests. You’ll find 11 out of 10 of them would rather take over many simpler classes and modules than a single class or module that does everything. Of course, finding useful abstractions in the code itself is hard work (but fun work). You’ve got to really weigh what’s in play in your mind and tease apart things into groups of related behavior, or related change, or related themes. It’s a skill in and of itself that must be practiced. So, when frontend tools started to mature and a clean separation from the backend could be made, well, let’s just say that it moved some of the hard thinking out of our brains and into the system itself. Our brains were now free to focus on different problems.
We can now use tools better equipped to aide in the frontend development when writing the frontend application, and we can focus our backend tools on solving problems they are really well suited for. This applies to implementation code and to unit testing. But, what happens when you want to test your application full-stack, end-to-end (e2e)?
- Do you go with the frontend framework’s end-to-end technology choice (such as Angular’s suggestion of Protractor)?
- Do you go with a testing stack that works well with the backend technology (such RSpec and Capybara with Ruby on Rails)?
- Do you go with a different technology & testing stack altogether (such as Python’s Golem)?
The truth is: It doesn’t really matter. Most of these use the same underlying tools such as Selenium WebDriver and a browser (Chrome, Firefox, Safari/Webkit, IE/Edge, etc). The best answer for you and your team likely depends on you and your team.
This is why for a recent Angular+Rails project we chose to use a testing stack from the backend technology’s ecosystem for e2e testing.
If you and your team find yourself wondering about similar things then this series of posts will be a good read.
What to expect in this series
This series focuses on setting up an e2e test suite for an application that uses an Angular 6 (versions 5 or 4 should work just fine as well) frontend and a Rails 5 API backend. Rather than just being focused on nuts and bolts it also provides additional context because knowing the why behind a decision can often be more helpful than the mechanics. In this series I try to do both.
The walkthrough itself generates new Angular and Rails apps, focuses on setting things up for end-to-end testing, and does not build out application functionality. My assumption is that you’ll get the most value out of setting up end-to-end testing rather than building out features.
By following this walkthrough you’ll have a barebones project successfully setup for end-to-end testing.
This gives you a working reference point for applying the same steps to your own project.
The sample application built in this walkthrough will be shared to Github and a link to that repository will be provided Part 2.
Whereas Part 2 is going to focus on the mechanics the rest of Part 1 is going to focus on our whys for using backend technology for end-to-end testing.
Why did we use the backend technology stack for end-to-end tests?
We used testing tools that were in the same ecosystem as our backend technology stack for primrily three reasons:
- We owned both ends of the stack
- Team experience
- Interacting with the database
Here’s a summary of our conversations and thinking.
Why owning both ends of the stack was a contributing factor
This meant that we owned both sides of the product implementation. For unit testing on the frontend, we stayed with Angular’s suggestion of Jasmine. For unit testing on the backend, we went with rspec-rails. These worked well since unit tests don’t need to cross technology boundaries. When it came to testing the whole product, end-to-end, owning both sides gave us not only more options to consider, but also more tools to choose from.
To help narrow it down we talked about what our team was trying to accomplish and what would help us get there. One of the first thing discussed was our individual and collective experience with the various testing tools available to us.
Why team experience was a contributing factor
There are times to stretch individually and as a team, but there are also times to take advantage of what you already know.
This particular project team came in with a lot of experience using testing tools like RSpec and Capybara. This included integrating with additional tools like Selenium WebDriver, Chrome and Chromedriver, data generation libraries like FactoryBot, and task runners like Rake. We had less experience doing end-to-end testing with Protractor even though it too uses Selenium WebDriver (a tool we’re very comfortable with).
We did a spike into using Protractor to drive our end-to-end tests and it worked well locally, but we ran into some issues with it running on Heroku CI. This spike was timeboxed and rather than continue down this path we decided to take a step back and evaluate what we were trying to accomplish.
The problem domain and the data involved in this project was complicated enough. We decided that not having to worry about unknowns with the frontend end-to-end testing stack helped mitigate risk. This isn’t to say you should always going with the tool you know, but in this instance we felt it was the right choice.
Another contributing factor we considered was how we were going to setup test data.
Why interacting with the database was a contributing factor
We were not strictly blackbox testing our application. We wanted to simulate a user walking thru specific scenarios in the app which required that we have corresponding data in the database. This helps ensure integration between the frontend and backend was wired up successfully and would give us a foundation for testing critical user flows. Rather than write new tooling we decided to take advantage of tooling we had in place for our unit tests.
Our unit tests already used FactoryBot, a test data generation library, for building up test datasets for a variety of test scenarios. Plus, we had already built up a nice suite of helpers that we coud re-use.
By using tools and libraries already a part of the backend technology’s ecosystem we were able to spend less time building additional tooling. We had less code to maintain because of this and more time to work on solving our customer’s pain points.
With all of that said, you now know the reasons behind our decision. You may or may not agree with it, but perhaps some of the questions we asked ourselves will resonate with you. Who knows, maybe they’ll even be helpful as you and your team bring an application into the world.
I’ll see you in Part 2 where we’ll be setting up our end-to-end test suite.