@RunWith(JMock.class) public class FormatTransactionAsCsvRowTest { private final Mockery mockery = new Mockery(); private final CsvFormat<LocalDate> dateFormat = mockery.mock(CsvFormat.class, "date format"); private final CsvFormat<Category> categoryFormat = mockery.mock(CsvFormat.class, "category format"); private final CsvFormat<Amount> amountFormat = mockery.mock(CsvFormat.class, "amount format"); private final TransactionCsvFormat transactionCsvFormat = new TransactionCsvFormat(dateFormat, categoryFormat, amountFormat); private LocalDate anyNonNullDate = new LocalDate(2012, 11, 14); private Category anyNonNullCategory = new Category("Bowling Winnings"); private Amount anyNonNullAmount = Amount.cents(250); @Test public void happyPath() throws Exception { mockery.checking( new Expectations() { { allowing(dateFormat).format(with(any(LocalDate.class))); will(returnValue("::the date::")); allowing(categoryFormat).format(with(any(Category.class))); will(returnValue("::the category::")); allowing(amountFormat).format(with(any(Amount.class))); will(returnValue("::the amount::")); } }); final Transaction transaction = new Transaction(anyNonNullDate, anyNonNullCategory, anyNonNullAmount); final String rowText = transactionCsvFormat.format(transaction); assertThat( rowText, matches( Pattern.compile( "\\s*\"::the date::\"," + "\\s*\"::the category::\"," + "\\s*\"::the amount::\"\\s*"))); } @Test public void nullTransaction() throws Exception { try { transactionCsvFormat.format(null); fail("How did you format a null transaction?!"); } catch (ProgrammerMistake success) { } } }
@Test public void happyPath() throws Exception { mockery.checking( new Expectations() { { allowing(dateFormat).format(with(any(LocalDate.class))); will(returnValue("::the date::")); allowing(categoryFormat).format(with(any(Category.class))); will(returnValue("::the category::")); allowing(amountFormat).format(with(any(Amount.class))); will(returnValue("::the amount::")); } }); final Transaction transaction = new Transaction(anyNonNullDate, anyNonNullCategory, anyNonNullAmount); final String rowText = transactionCsvFormat.format(transaction); assertThat( rowText, matches( Pattern.compile( "\\s*\"::the date::\"," + "\\s*\"::the category::\"," + "\\s*\"::the amount::\"\\s*"))); }
@Test public void modelBlowsUpInAnUnavoidableWay() throws Exception { mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); will(throwException(new InternalStorageException())); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "Something strange just happened. Try again. You might need to reinstall the " + "application. I feel embarrassed and ashamed."); }
@Test public void exportWhateverTheModelProvides() throws Exception { final List<Transaction> anyNonEmptyListOfTransactions = ObjectMother.anyNonEmptyListOfTransactions(); mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); will(returnValue(anyNonEmptyListOfTransactions)); ignoring(androidDevicePublicStorageGateway); oneOf(exportAllTransactionsAction).execute(with(anyNonEmptyListOfTransactions)); } }); pressExportAllTransactionsButton(browseTransactionsActivity); }
@Test public void mediaNotAvailable() throws Exception { mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); allowing(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory(); will(throwException(new PublicStorageMediaNotAvailableException())); never(exportAllTransactionsAction); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "No place to which to export the transactions. Insert an SD card or connect an " + "external storage device and try again."); }
@Test public void exportActionBlowsUpInAnUnavoidableWay() throws Exception { mockery.checking( new Expectations() { { // SMELL How can I ignore these irrelevant details? ignoring(browseTransactionsModel); ignoring(androidDevicePublicStorageGateway); allowing(exportAllTransactionsAction).execute(with(any(List.class))); will(throwException(new InternalStorageException())); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "Something strange just happened. Try again. You might need to reinstall the " + "application. I feel embarrassed and ashamed."); }
@Test public void mediaNotWritable() throws Exception { mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); allowing(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory(); will( throwException( new PublicStorageMediaNotWritableException( new File("/mnt/sdcard/TrackEveryPenny.csv")))); never(exportAllTransactionsAction); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "Permission denied trying to export the transactions to file " + "/mnt/sdcard/TrackEveryPenny.csv"); }
@Test public void happyPath() throws Exception { final Collection<Object> anyValidNonTrivialCollectionOfTransactions = Lists.newArrayList(new Object(), new Object(), new Object()); mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); will(returnValue(anyValidNonTrivialCollectionOfTransactions)); // SMELL Irrelevant detail ignoring(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory(); allowing(exportAllTransactionsAction).execute(with(any(List.class))); // succeeds by not throwing an exception } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex("Exported all transactions to (.+)\\.csv"); }
@RunWith(RobolectricTestRunner.class) public class HandleExportAllTransactionsTest { private Mockery mockery = new Mockery(); private final BrowseTransactionsModel browseTransactionsModel = mockery.mock(BrowseTransactionsModel.class); private final ExportAllTransactionsAction exportAllTransactionsAction = mockery.mock(ExportAllTransactionsAction.class); private final AndroidDevicePublicStorageGateway androidDevicePublicStorageGateway = mockery.mock(AndroidDevicePublicStorageGateway.class); private final BrowseTransactionsActivity browseTransactionsActivity = new BrowseTransactionsActivity( null, exportAllTransactionsAction, androidDevicePublicStorageGateway, browseTransactionsModel); @Before public void initializeActivity() { browseTransactionsActivity.onCreate(null); browseTransactionsActivity.setCollaborators( exportAllTransactionsAction, androidDevicePublicStorageGateway, browseTransactionsModel); } @Test public void happyPath() throws Exception { final Collection<Object> anyValidNonTrivialCollectionOfTransactions = Lists.newArrayList(new Object(), new Object(), new Object()); mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); will(returnValue(anyValidNonTrivialCollectionOfTransactions)); // SMELL Irrelevant detail ignoring(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory(); allowing(exportAllTransactionsAction).execute(with(any(List.class))); // succeeds by not throwing an exception } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex("Exported all transactions to (.+)\\.csv"); } @Test public void mediaNotAvailable() throws Exception { mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); allowing(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory(); will(throwException(new PublicStorageMediaNotAvailableException())); never(exportAllTransactionsAction); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "No place to which to export the transactions. Insert an SD card or connect an " + "external storage device and try again."); } @Test public void mediaNotWritable() throws Exception { mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); allowing(androidDevicePublicStorageGateway).findPublicExternalStorageDirectory(); will( throwException( new PublicStorageMediaNotWritableException( new File("/mnt/sdcard/TrackEveryPenny.csv")))); never(exportAllTransactionsAction); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "Permission denied trying to export the transactions to file " + "/mnt/sdcard/TrackEveryPenny.csv"); } @Test public void modelBlowsUpInAnUnavoidableWay() throws Exception { mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); will(throwException(new InternalStorageException())); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "Something strange just happened. Try again. You might need to reinstall the " + "application. I feel embarrassed and ashamed."); } @Test public void exportActionBlowsUpInAnUnavoidableWay() throws Exception { mockery.checking( new Expectations() { { // SMELL How can I ignore these irrelevant details? ignoring(browseTransactionsModel); ignoring(androidDevicePublicStorageGateway); allowing(exportAllTransactionsAction).execute(with(any(List.class))); will(throwException(new InternalStorageException())); } }); pressExportAllTransactionsButton(browseTransactionsActivity); assertLastToastMatchesRegex( "Something strange just happened. Try again. You might need to reinstall the " + "application. I feel embarrassed and ashamed."); } @Test public void exportWhateverTheModelProvides() throws Exception { final List<Transaction> anyNonEmptyListOfTransactions = ObjectMother.anyNonEmptyListOfTransactions(); mockery.checking( new Expectations() { { allowing(browseTransactionsModel).findAllTransactions(); will(returnValue(anyNonEmptyListOfTransactions)); ignoring(androidDevicePublicStorageGateway); oneOf(exportAllTransactionsAction).execute(with(anyNonEmptyListOfTransactions)); } }); pressExportAllTransactionsButton(browseTransactionsActivity); } private void pressExportAllTransactionsButton( BrowseTransactionsActivity browseTransactionsActivity) { browseTransactionsActivity.exportAllTransactions( browseTransactionsActivity.findViewById(R.id.exportAllTransactionsButton)); } public static void assertLastToastMatchesRegex(String patternText) { ShadowHandler.idleMainLooper(); assertThat(ShadowToast.getTextOfLatestToast(), matches(Pattern.compile(patternText))); } }