This article was adapted from a Google Testing on the Toilet (TotT) episode. You can download a printer-friendly version of this TotT episode and post it in your office.
SpeedyImgImage decodeImage(List<SpeedyImgDecoder> decoders, byte[] data) { SpeedyImgOptions options = getDefaultConvertOptions(); for (SpeedyImgDecoder decoder : decoders) { SpeedyImgResult decodeResult = decoder.decode(decoder.formatBytes(data)); SpeedyImgImage image = decodeResult.getImage(options); if (validateGoodImage(image)) { return image; } } throw new RuntimeException(); }
Image decodeImage(List<ImageDecoder> decoders, byte[] data) { for (ImageDecoder decoder : decoders) { Image decodedImage = decoder.decode(data); if (validateGoodImage(decodedImage)) { return decodedImage; } } throw new RuntimeException(); }
“Separation of Concerns” in the context of external APIs is also described by Martin Fowler in his blog post, Refactoring code that accesses external services.
describe('Terms of service are handled', () => { it('accepts terms of service', async () => { const user = getUser('termsNotAccepted'); await login(user); await see(termsOfServiceDialog()); await click('Accept') await logoff(); await login(user); await not.see(termsOfServiceDialog()); }); });
describe('Terms of service are handled', () => { it('accepts terms of service', async () => { const user = getUser('someUser'); await hook('TermsOfService.Get()', true); await login(user); await see(termsOfServiceDialog()); await click('Accept') await logoff(); await hook('TermsOfService.Get()', false); await login(user); await not.see(termsOfServiceDialog()); }); });
public class FakeTermsOfService implements TermsOfService.Service { private static final Map<String, Boolean> accepted = new ConcurrentHashMap<>(); @Override public TosGetResponse get(TosGetRequest req) { return accepted.getOrDefault(req.UserID(), Boolean.FALSE); } @Override public void accept(TosAcceptRequest req) { accepted.put(req.UserID(), Boolean.TRUE); } }
describe('Terms of service are handled', () => {
it('accepts terms of service', async () => { const user = getUser('termsNotAccepted'); await login(user); await see(termsOfServiceDialog()); await click('Accept') await logoff(); await login(user); await not.see(termsOfServiceDialog()); }); });
<button disabled click=”$handleBuyClick(data)”>Buy</button>
it('submits purchase request', () => { controller = new PurchasePage(); // Call the method that handles the "Buy" button click controller.handleBuyClick(data); expect(service).toHaveBeenCalledWith(expectedData); });
it('submits purchase request', () => { // Renders the page with the “Buy” button and its associated code. render(PurchasePage); // Tries to click the button, fails the test, and catches the bug! buttonWithText('Buy').dispatchEvent(new Event(‘click’)); expect(service).toHaveBeenCalledWith(expectedData); });
// Declared in the module. constexpr int kThumbnailSizes[] = {480, 576, 720}; // Returns thumbnails of various sizes for the given image. std::vector<Image> GetThumbnails(const Image& image) { std::vector<Image> thumbnails; for (const int size : kThumbnailSizes) { thumbnails.push_back(ResizeImage(image, size)); } return thumbnails; }
std::vector<Image> GetThumbnails(const Image& image, absl::Span<const int> sizes) { std::vector<Image> thumbnails; for (const int size : sizes) { thumbnails.push_back(ResizeImage(image, size)); } return thumbnails; }
// Declared in the public header. inline constexpr int kDefaultThumbnailSizes[] = {480, 576, 720}; // Default argument allows the function to be used without specifying a size. std::vector<Image> GetThumbnails(const Image& image, absl::Span<const int> sizes = kDefaultThumbnailSizes);
// Mock a salary payment library @Mock SalaryProcessor mockSalaryProcessor; @Mock TransactionStrategy mockTransactionStrategy; ... when(mockSalaryProcessor.addStrategy()).thenReturn(mockTransactionStrategy); when(mockSalaryProcessor.paySalary()).thenReturn(TransactionStrategy.SUCCESS); MyPaymentService myPaymentService = new MyPaymentService(mockSalaryProcessor); assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);
FakeSalaryProcessor fakeProcessor = new FakeSalaryProcessor(); // Designed for tests MyPaymentService myPaymentService = new MyPaymentService(fakeProcessor); assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);
@Mock MySalaryProcessor mockMySalaryProcessor; // Wraps the SalaryProcessor library ... // Mock the wrapper class rather than the library itself when(mockMySalaryProcessor.sendSalary()).thenReturn(PaymentStatus.SUCCESS); MyPaymentService myPaymentService = new MyPaymentService(mockMySalaryProcessor); assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);