
Advanced Java Programming Dependencies Among Objects Consider the below set of dependent objects The Dependency Injection design pattern decouples dependent objects so that they may be configured and tested independently. Google BookInventory BookStore CreditCardService remove(Book) purchase(List<Book>, CreditCard) : double debit(CreditCard, dollars : int, cents : int) Guice manages dependencies among objects add(Book) refund(List<Book> CreditCard) : double credit(CreditCard, dolars : int, cents : int) and handles the complexity of wiring together inStock(Book) : int complex object relationships. REST FirstBankOfPSU BookDatabase FirstBankOfPortlandStateServlet serverHost : String directory : File serverPort : int Dependency Injection fileName : String The Dependency Injection design pattern • A BookStore requires a BookInventory and a Testing with mock objects using Mockito CreditCardService to get its work done • Managing dependencies with Google Guice We’ve already extracted interfaces to allow us to • • abstract out the behavior that we need We want to be able to replace the implementations of • BookInventory and CreditCardService without affecting the code in BookStore Copyright ©2010-2021 by David M. Whitlock. Permission to make digital or hard copies of part or all of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and full citation on the first page. To copy otherwise, to republish, to post on servers, or to redistribute to lists, requires prior specific permission and/or fee. Request permission to publish from [email protected]. Last updated March 13, 2021. 1 2 How can we implement BookStore? How can we implement BookStore? package edu.pdx.cs410J.di; public double purchase( List<Book> books, CreditCard card) { import java.io.File; double total = 0.0d; import java.util.List; for (Book book : books) { inventory.remove(book); public class BookStore total += book.getPrice(); { } private final BookInventory inventory; CreditTransactionCode code = private final CreditCardService cardService; cardService.debit(card, total); if (code == CreditTransactionCode.SUCCESS ) { public BookStore() { return total; String tmpdir = } else { System.getProperty( "java.io.tmpdir" ); throw new CreditCardTransactionException(code); File directory = new File( tmpdir ); } this.inventory = } new BookDatabase( directory, "books.txt" ); this.cardService = The purchase method uses the dependencies new FirstBankOfPSU( "localhost", 8080 ); } When the BookStore is created, it also creates its dependencies Dependencies are stored in final fields because • they only need to be created once 3 4 What is wrong with this solution? “Don’t call us, we’ll call you” Sure, this code works, but.. The “Hollywood Principle” states that dependent code should require that its dependencies are provided to it You can’t test this code! • Practically speaking, it means pass dependencies – Tests would have to execute against the actual • into the constructor database (slow) and credit card service ($$$) public BookStore( BookInventory inventory, – Lots of data setup: create database file and credit CreditCardService cardService ) { card account this.inventory = inventory; this.cardService = cardService; The BookStore has to know how to configure its } • dependencies Now the BookStore class doesn’t have to know about the – Does the BookStore really care what port the concrete types of its dependencies credit card service runs on? Let the main program create and configure • dependencies If another class wants to use the BookDatabase, it • would have to create another instance public class BookStoreApp { public static void main(String... args) { – Two BookDatabases can’t work with the same file String tmpdir = System.getProperty( "java.io.tmpdir" ); File directory = new File( tmpdir ); If you want to switch out another implementation of BookInventory inventory = • the dependencies, you have to change the new BookDatabase( directory, "books.txt" ); BookStore CreditCardService cardService = new FirstBankOfPSU( "localhost", 8080 ); – Again, does the BookStore really care what kind BookStore store = of BookInventory is uses? new BookStore(inventory, cardService); 5 6 Testing with Mock Objects Testing with Mock Objects Now that a BookStore gets passed its dependencies, you Test that BookStore invokes the expected methods of its can test the class without using the “production” dependencies BookInventory and CreditCardService Override interface methods to keep track of how it • Instead of using real dependencies, test with “mock” was invoked objects that provide an implementation of the interface package edu.pdx.cs410J.di; that is useful for testing import org.junit.Test; package edu.pdx.cs410J.di; import java.util.Collections; import static org.junit.Assert.assertEquals; public abstract class MockObject { import static org.junit.Assert.assertNotNull; protected void shouldNotInvoke() { throw new UnsupportedOperationException( public class BookStoreTest { "Did not expect this method to be invoked"); @Test } public void testBookIsPurchased() { } final Book[] removedBook = new Book[1]; BookInventory inventory = new MockBookInventory() { public void remove( Book book ) { package edu.pdx.cs410J.di; removedBook[0] = book; } public class MockBookInventory extends MockObject }; implements BookInventory { CreditCardService cardService = new MockCreditCardService() { public CreditTransactionCode debit( CreditCard card, double amount ) { public void remove( Book book ) { return CreditTransactionCode.SUCCESS; shouldNotInvoke(); } }; } } 7 8 Testing with Mock Objects Mock Objects are Awkward Once the mock dependencies are set up Mock Objects get the job done, but... BookStore Create the Lots of verbose boilerplate code • • Invoke the method you want to test • When interface changes (new method, e.g.), mock • Verify that the dependency was invoked as you has to change • expected Subclassing mock objects is hokey BookStore store = • new BookStore(inventory, cardService); – Lots of anonymous inner classes CreditCard card = new CreditCard( "123" ); final Book testBook = – Have to keep track of state that was passed to new Book("title", "author", 1.00d); dependency double total = store.purchase( Collections.singletonList(testBook), card ); assertEquals( testBook.getPrice(), total, 0.0d ); There’s got to be a better way, right? final Book removed = removedBook[0]; assertNotNull( removed ); assertEquals(testBook.getTitle(), removed.getTitle()); assertEquals(testBook.getAuthor(), removed.getAuthor()); assertEquals(testBook.getPrice(), removed.getPrice(), 0.0d); } } 9 10 Mock Objects with Mockito Mock Objects with Mockito There are several testing libraries for Java that allow you @Test to easily create mock objects public void testCreditCardIsCharged() { Book book = mock(Book.class); double price = 1.00d; We’re going to learn about Mockito (http://mockito.org/) when( book.getPrice() ).thenReturn( price ); A Java API that creates mock objects for interfaces • BookInventory inventory = and classes mock(BookInventory.class); Contains a domain specific language (DSL) for • CreditCardService cardService = constructing mock objects mock(CreditCardService.class); when(cardService.debit(any( CreditCard.class ), You can specify what behavior you expect out of the • anyDouble() )) mock objects .thenReturn( CreditTransactionCode.SUCCESS ); Add the dependency in your pom.xml BookStore store = <dependency> new BookStore(inventory, cardService); <groupId>org.mockito</groupId> CreditCard card = mock(CreditCard.class); <artifactId>mockito-core</artifactId> <version>1.8.5</version> double total = store.purchase( <scope>test</scope> Collections.singletonList(book), card ); </dependency> assertEquals( book.getPrice(), total, 0.0d ); verify( cardService ).debit( card, price ); } Let’s take a closer look... 11 12 Creating mock objects Specifying behavior of mock objects The most interesting methods are all in the The when method specifies how a method of the mock org.mockito.Mockito class object should behave static <T> OngoingStubbing<T> when(T The mock method creates a mock object for an interface • or class methodCall) static <T> T mock(Class<T> classToMock) Because they cannot be overridden by the magical • • class loader, final methods cannot be sent to when Unless instructed otherwise, invoking a method of • the mock object will noop or return null The OngoingStubbing class specifies what the behavior of the mock method Mockito uses crazy class loading tricks to accomplish thenReturn(T value) will cause the mock method • this • to return a given value thenThrow(Throwable t) We mocked all of our objects in the previous example will throw an exception • when the mock method is invoked No need to invoke constructors • thenCallRealMethod() will invoked the real Only had to specify the state we needed • implementation of the mock object to be invoked • – The test doesn’t care about the title or author In general, you want to chain the when and then calls of the Book together We can add a method to an interface without having when( book.getPrice() ).thenReturn( price ); • to update a mock implementation 13 14 Method Argument Matching Method Argument Matching The same mock method can be configured with different If at least one arguments to a mock method is one of the argument values “matchers”, then all arguments must be matchers Use the eq() methods for matching on a single value when( cardService.debit(card,
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages8 Page
-
File Size-