Procore has grown quickly, and our testing methods must scale proportionate to that growth. As many organizations do, we sought a better tool for ensuring we only release quality code into production. We found and implemented Mirage JS and we want to walk our audience through the process that led us to implement Mirage and explain how it improved our ability to get quality code into production more rapidly.

Our team, Communication Tools, works on Submittals and RFIs. Due to high usage and frequent cross-team collaborations, we must be cautious about the changes we deploy and must maintain a thorough testing process. Even a small bug requires a large support response due to the number of users relying on our tools each day.


An audit of our current test strategy revealed that an initial investment in automation would require significant time and effort since the front-end’s automated test coverage consisted primarily of basic render tests, with a few areas of more in-depth integration testing. We were a long way from the amount of coverage necessary to meaningfully reduce the need for manual testing—an untenable amount of work if we didn’t invest in automation.


At the same time, we also shifted to a decoupled architectural vision. Many Procore front-end tools relied on tightly coupled touch-points with the Rails back end. The organization was looking to a future where the front-end could function almost completely separate from the back-end. Decoupling and testing might seem like separate concerns, but our team saw the opportunity to combine goals, creating a more robust automation suite and front-end code that could function independently. We were able to experiment with new technologies to gain new ground in quality assurance.

Decoupling the front-end from the back-end brings a number of challenges and questions. Where should the balance lie between end-to-end tests vs. unit and integration tests? Can existing tests be converted for the new implementation, or is it best to write new tests as you go? What percentage of test coverage is sufficient to feel confident in the automation suite as a whole?

The experimental solution we chose, Mirage JS, also sped up our local development experience.  Mirage is an API mocking library with various options for recreating back-end functionality in a completely decoupled front-end environment.

Setting up a Mirage server in our team’s codebase was a worthy investment. We were excited to see what more could be done by using our server for unit and integration tests. What we soon realized was we couldn’t rely on hard coding alone.


There are a few tools built into Mirage you can use to emulate the back-end experience in your decoupled front-end environment:

  • Route handlers to intercept HTTP requests and return the data you would expect from a backend API.
  • Serializers for formatting the route handler payloads and responses
  • A database for storing test or local development data.
  • Models, factories, and fixtures for defining relationships and creating mock data to populate your database.

Instead of hitting an endpoint and mocking the exact response each time in your tests, these tools allow for a more one-to-one experience with production behavior between the front end and the back end. Emulating this kind of dynamic behavior becomes simple after setting up a Mirage server, and it provides more opportunities for comprehensive testing with a bias towards unit and integration tests.

While decoupling and rewriting some of our front-end code, our team drastically increased our test coverage and confidence in the code we ship by taking advantage of Mirage’s dynamic test behavior.

Our team could easily mock the interactions between connected fields, where one field’s value modifies another using data stored in the testing database. In the case of a location picker, we can create new locations on the fly, store them in the database, and use them as selections later in the test. We can even load fixtures and factories for entire submittals and edit those submittals in practically every way you could modify a submittal in a production environment.

The test environment is more dynamic, but the front-end code stays the same regardless of whether it’s interacting with Mirage or Rails. The server runs alongside front-end code without requiring manual management or resets. Different data scenarios can be tested without rewriting route handlers and mocks to suit every individual scenario.

This was a boost to our testing capabilities. However, more importantly still, we learned about concepts less prevalent in front-end development, such as models, schemas, and serializers. This allowed us to understand more acutely the backend we work with and the opportunities Mirage provides.


Embracing automated test tooling and methodology gave our team ownership of the quality of our code, allowing us to grow as developers and bring new opportunities to front-end testing as a whole. Procore encourages us to keep learning, keep trying new things, and keep looking toward the future. Working with Mirage to expand our testing coverage and speed up our development as well as QA processes turned out to be a worthwhile experience.

Our team is excited to see what more we can do with Mirage in the future and how it might be adopted elsewhere in Procore. For us, Mirage was an experiment that helped redefine our expectations of what front-end testing can be: dynamic, decoupled, and data-driven.

If this kind of challenge excites you, then maybe you should come work for us!