Getting on the refactor tractor

Safer refactoring

  • Characterisation tests
  • Snapshot tests

Characterisation tests

testing what the behaviour of the system _is_, not whether it's correct even if you've already got good test coverage, you might want to consider adding some because tests usually only cover _intended_ behaviour
Working effectively with legacy code, by Michael Feathers
Feature test Characterisation test
  1. Write test with expectations
  1. Write test with no expectations
  1. Write code to make test pass
  1. Change expectations until test passes
Code changes, test stays the same Test changes, code stays the same
Of, if you're not into TDD, write code, write tests, go back and change code until tests pass, consider your life choices and how much time you would have saved if you'd skipped the first step

function formatDate(date) {
  if (!date) return ""

  return moment(new Date(date)).format("yyyy-MM-D")
}
          
1 for 1 replacement maintain all existing behaviour some risk - can take a few different kinds of input also, there's a bug in the code that we want to preserve
👩🏽‍💻
start with momentjs format function tests - undefined - a date - a number - a string - a string that isn't a date - a string that's an invalid date - the number zero there are other test cases we could write (eg passing in a a boolean, cos that's going to behave weirdly, but if you're passing booleans into a date formatting function something else has gone terribly wrong. also, use typescript) switch the library are there any differences? how will we deal with them? would i really write all these tests for such a small function? - if it was only used once, no, i'd just write the likely cases - if it was used everywhere, then, yes

JSX

function UserInfo({ user }) {
	const date = formatDate(user.birthday)
	const day = moment(user.birthday).format("dddd")

	return (
		
{user.name} was born on {date} ({day})
) }
- continuing with our moment to dayjs refactor, we can take the same approach as before...
👩🏽‍💻
write some tests (use "container") do the refactor but also, when we're rendering components we have some other tools available to us

Snapshot Tests

  1. Generate a snapshot
  2. Make changes
  3. Check against the snapshot
snapshot = text rendering of component step 3 = generate a new snapshot & do a diff
👩🏽‍💻
easier just to show - write snapshot test of UserInfo - run test to generate snapshot - show where snapshot is created and what it looks like - change something in the test show what happens when the snapshot fails & how to update it
more complicated example - clicking on a button the increments a count

Snapshot test gotchas

  • async rendering

exports[`renders something asynchronously`] = `
<div />
`;
          

Snapshot test gotchas

  • async rendering
  • not everything is rendered

exports[`updates the state of the checkbox`] = `
<div>
  <input
    type="checkbox"
  />
  </div>
`;
          

ye olde farme shoppe

New requirements!

  • New product type radio button: plant
  • Show unit + total price for plant in label ($15)
  • Calculate postage for plants
have a look at the code and why we might want to refactor it
👩🏽‍💻
- snapshot tests of whole component (inc seeds/seedlings, no amount, amount) - refactor to CountInput - code new plants option

What did we learn?

  • Make the change easy, make the easy change
  • Change implementation, not functionality
  • Characterisation tests
  • Snapshot tests
@ErinJZimmer
@ErinJZimmer