@Test public void pickFruits() { final FruitTree mangoTree = context.mock(FruitTree.class); final Mango mango1 = new Mango(); final Mango mango2 = new Mango(); context.checking( new Expectations() { { oneOf(mangoTree).pickFruit(with(any(Collection.class))); will( new VoidAction() { public Object invoke(Invocation invocation) { @SuppressWarnings({"unchecked"}) Collection<Fruit> fruits = (Collection<Fruit>) invocation.getParameter(0); fruits.add(mango1); fruits.add(mango2); return null; } }); } }); Collection<Fruit> fruits = new FruitPicker().pickFruits(asList(mangoTree)); assertEquals(asList(mango1, mango2), fruits); }
@Test public void willAnnounceItHasFinishedIfPriceGoesAboveMaximum() { context.checking( new Expectations() { { exactly(1).of(listener).sniperFinished(sniper); } }); sniper.bidAccepted(unbeatableBid); }
@Test public void willLimitBidToMaximum() throws Exception { context.checking( new Expectations() { { exactly(1).of(auction).bid(maximumBid); } }); sniper.bidAccepted(maximumBid.subtract(new Money(1))); }
@Test public void willNotBidPriceGreaterThanMaximum() throws Exception { context.checking( new Expectations() { { ignoring(listener); never(auction).bid(with(any(Money.class))); } }); sniper.bidAccepted(unbeatableBid); }
@Test public void triesToBeatTheLatestHighestBid() throws Exception { final Money expectedBid = beatableBid.add(increment); context.checking( new Expectations() { { oneOf(auction).bid(expectedBid); } }); sniper.bidAccepted(beatableBid); }
@Test public void catchesExceptionsAndReportsThemToErrorListener() throws Exception { final AuctionException exception = new AuctionException("test"); context.checking( new Expectations() { { allowing(auction).bid(with(any(Money.class))); will(throwException(exception)); exactly(1).of(listener).sniperBidFailed(sniper, exception); } }); sniper.bidAccepted(beatableBid); }
@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))); } }
/** * This and other classes in this package (except, naturally, AuctionSniper_JMockit_Test) are based * on the original source code available <a * href="http://svn.jmock.codehaus.org/browse/jmock/trunk/jmock2/example/org/jmock/example/sniper">here</a>. * Small modifications were made to simplify the code, without compromising the original design. */ @RunWith(JMock.class) public final class AuctionSniperTest { final Mockery context = new JUnit4Mockery(); final Money increment = new Money(2); final Money maximumBid = new Money(20); final Money beatableBid = new Money(10); final Money unbeatableBid = maximumBid.add(new Money(1)); final Auction auction = context.mock(Auction.class); final AuctionSniperListener listener = context.mock(AuctionSniperListener.class, "listener"); final AuctionSniper sniper = new AuctionSniper(auction, increment, maximumBid, listener); @Test public void triesToBeatTheLatestHighestBid() throws Exception { final Money expectedBid = beatableBid.add(increment); context.checking( new Expectations() { { oneOf(auction).bid(expectedBid); } }); sniper.bidAccepted(beatableBid); } @Test public void willNotBidPriceGreaterThanMaximum() throws Exception { context.checking( new Expectations() { { ignoring(listener); never(auction).bid(with(any(Money.class))); } }); sniper.bidAccepted(unbeatableBid); } @Test public void willLimitBidToMaximum() throws Exception { context.checking( new Expectations() { { exactly(1).of(auction).bid(maximumBid); } }); sniper.bidAccepted(maximumBid.subtract(new Money(1))); } @Test public void willAnnounceItHasFinishedIfPriceGoesAboveMaximum() { context.checking( new Expectations() { { exactly(1).of(listener).sniperFinished(sniper); } }); sniper.bidAccepted(unbeatableBid); } @Test public void catchesExceptionsAndReportsThemToErrorListener() throws Exception { final AuctionException exception = new AuctionException("test"); context.checking( new Expectations() { { allowing(auction).bid(with(any(Money.class))); will(throwException(exception)); exactly(1).of(listener).sniperBidFailed(sniper, exception); } }); sniper.bidAccepted(beatableBid); } }