Mock-Driven Development is a way of planning out features in a way that allows each dev to work on a feature without having to wait for other coders to finish their work.
- The coder is given a business requirement and told "make this happen".
- Because the business scope might be vague, the technical scope becomes a big question mark.
- This often makes the scope of the feature cross several domains of the app (backend, frontend, API, database, etc.)
- By the end of the feature, the coder is expected to have all these domains working together without errors.
- You have a team of 1.
- You're designing a "hello world" app.
- You love pain and suffering.
- You have a team of >2.
- You're working on a complex app that has a lot of moving parts.
- You don't love pain and suffering.
-
blocks other coders because they can't jump in
-
if the coder gets stuck, the entire feature is blocked.
-
the coder is often saying "it's almost done!" for weeks or months
-
it's draining to work on a feature for so long
-
by the time they're done, the feature is so big that it's hard to review
Example of how a task like "add a new field to the form" becomes a planning disaster:
- is it just adding the field to the database?
- or is it also adding the field to the UI?
- or is it also adding the field to the serializer?
- or is it also adding the field to the backend?
- or is it also adding the field to the frontend?
- or is it also adding the field to the backend and the frontend?
- or is it also adding the field to the backend and the frontend and the API?
- or is it also adding the field to the backend and the frontend and the API and the UI?
- or is it also adding the field to the backend and the frontend and the API and the UI and the database?
- ...etc.
- other coders can jump in and help
- tasks are not so complex that only the ancient code wizard who lives on the mountain will know how to implement it
- small reviews
- ship faster
- take a business story that has already been groomed by the product owner (with the help of the team)
- find points where the components in the software connect and define "contracts" between them
Example: the browser calls the API endpoint to create a payment agreement
- Each contract is divided up into two components: usually the "request" and the "response"
Example: the browser sends a request to the API endpoint and the API responds
- Each component agrees on the contract (the request and the response)
Example: calling to endpoint /api/xyz.json with the request body {a: 1, b: 2} will return the response body {c: 3, d: 4})
We want to clearly define the scope of work for each component so that the scope is manageable.
The components are going to assume that the other components respond exactly as they agreed to in the contract.
Here's another way to think about: we assume everything is broken (example: server is down) but we have to make the component work anyway.
- List the components that will be mocked out, using the contracts as a guide
Example: the API will be stubbed to respond with {c: 3, d: 4} when called with the request body {a: 1, b: 2}
- Provide the tool that will be used to mock each component
Example: MirageJS will be used to stub the API Example: nock() will be used to write tests Example: /public/api/xyz.json will be used to stub the API
- Provide the actual files that will be worked on
Example:
- app/javascript/components/Form.jsx
- app/javascript/components/Form.test.jsx
- app/javascript/api/createAgreement.js
- app/javascript/api/createAgreement.test.js
- Specify a list of files that are outside the scope and should not be touched
- app/controllers/api/xyz_controller.rb
- app/models/payment_agreement.rb
- app/serializers/payment_agreement_serializer.rb
What most people call "test-driven development" is actually the opposite: It's test-REACTIVE development.
"Reactive" test-driven development is code first, tests later. "Real" test-driven development is tests first, code later.
We can make a list of tests as a way to plan the technical parts of our feature, this our coder a clear plan of where to start, and clarifies the scope of work.
> Example:
> - when createAgreement() is called with createAgreement(1,2), it should send a post request to /api/xyz.json with the request body {a: 1, b: 2}
> - add this to app/javascript/api/createAgreement.test.js
> - example of a test like this: https://github.com/Lexop/lexop/blob/0888ad6ffbf56c4d9a6266eee8fc906670ad0784/test/javascript/components/payment_portal/gateways/spreedly/api.test.jsx#L16-L45