EXPEDIA GROUP TECHNOLOGY — ENGINEERING

Healthy unit tests for healthy code bases

Photo by Nathan Dumlao on Unsplash

Reset the Mocks

const mockExternalStore = {
isValid: true
};

describe('UnderTest', () => {
it('is valid if external store is', () => {
const underTest = new UnderTest(mockExternalStore);
expect(underTest.isValid()).to.be.equal(true);
});

it('is not valid if external store is not', () => {
mockExternalStore.isValid = false;
const underTest = new UnderTest(mockExternalStore);
expect(underTest.isValid()).to.be.equal(false);
});
}
let mockExternalStore;

describe('UnderTest', () => {
beforeEach(() => {
mockExternalStore = {
isValid: true
};
});

it('is valid if external store is', () => {
// I assume mockExternalStore.isValid == true is the agreed starting scenario, so don't assign it again
const underTest = new UnderTest(mockExternalStore);
expect(underTest.isValid()).to.be.equal(true);
});

it('is not valid if external store is not', () => {
mockExternalStore.isValid = false;
const underTest = new UnderTest(mockExternalStore);
expect(underTest.isValid()).to.be.equal(false);
});
}

Be Careful with Exceptions

@Test
public void whenCallingForbiddenMethodThenThrowCustomException() {
try {
underTest.forbiddenMethod();
} catch (Exception e) {
assertTrue(e instanceof MyCustomException);
}
}
@Test(expectedException = MyCustomException.class)
public void whenCallingForbiddenMethodThenThrowCustomException() {
underTest.forbiddenMethod();
}
@Test
public void whenCallingForbiddenMethodThenThrowCustomException() {
try {
underTest.forbiddenMethod();
fail();
} catch (MyCustomException e) {
assertTrue(e.getCause() instanceof AnotherCustomException);
}
}

Prefer Explicit Test Data

public final String MY_KEY = "4e9aa2e2-0c29-4d01-a4fe-b464fd89ef74";

// Hashes input together with MY_KEY
public String hash(String input) {
...
}
@Test(dataProvider = "hashSamples")
public void hashingTest(String input, String expectedDigest) {
assertEquals(underTest.hash(input), expectedDigest);
}

@DataProvider
private Object[][] hashSamples() {
return new Object[][]{
{"input1", "E6d212KuLc0XvXsc"},
{"loooooonginpuuuuuuuuuut", "SCNb9HHscUPzCNHL"},
{"input+with-symbol$", "5ePJWwMwYwQBxLf9"},
{"", "aixwFwUnRmU1405D"},
{null, "4aHg05q4ftFzn7dX"}};
}
@Test(dataProvider = "hashSamples")
public void hashingTest(String input, String expectedDigest) {
assertEquals(underTest.hash(input), expectedDigest);
}

@DataProvider
private Object[][] hashSamples() {
return new Object[][]{
buildSample("input1", MY_KEY},
buildSample("loooooonginpuuuuuuuuuut", MY_KEY},
buildSample("input+with-symbol$", MY_KEY},
buildSample("", MY_KEY},
buildSample(null, MY_KEY}};
}

public Object[] buildSample(String input, String key) {
String digest = ... //Does this code look familiar?!?
return new Object[]{input, digest};
}

Now you are basically replicating production code in the test, which defeats the purpose of the test itself.

Do Not Change Production Code for the Sake of Tests

public class BlackBox {
public doHouseCleaning() {
sweepTheFloor()
// ... some more code
doTheLaundry()
// ... some more code
doTheDishes()
// ... some more code
}

private sweepTheFloor() {
// ... a long method
}

private doTheLaundry() {
// ... a very long method
}

private doTheDishes() {
// ... an extremely long method
}
}

Problem: testing all the nuances of BlackBox through doHouseCleaning() is extremely hard.

Solution: turn all private methods into package private, so you can test the smaller bits independently!

public class HouseCleaner {
public doHouseCleaning() {
broom.sweepTheFloor()
// ... some more code
washingMachine.doTheLaundry()
// ... some more code
dishWasher.doTheDishes()
// ... some more code
}
}

public class Broom {
public sweepTheFloor() {
// ...
}
}

public class WashingMachine {
public doTheLaundry() {
// ...
}
}

public class DishWasher {
public doTheDishes() {
// ...
}
}

Learn more about technology at Expedia Group

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store