Testing folytatás

Készítsünk egy Utils osztályt, melynek segítségével CSV-ből importálhatunk könyveket!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Utils {

    List<Book> importBooksFromCSV(Path path){
        List<Book> books = new ArrayList<>();
        try(BufferedReader br = Files.newBufferedReader(path)){
            String line;
            while((line = br.readLine()) != null){
                books.add(parseBook(line));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return books;
    }

    public Book parseBook(String line) {
        String[] tokens = line.split(";");

        return new Book(tokens[0], tokens[1], LocalDate.of(Integer.parseInt(tokens[2]), Integer.parseInt(tokens[3]),
                Integer.parseInt(tokens[4])));
    }
}

Ezután nézzük meg, hogy hogyan tudjuk mockolni ezt az osztályt! Ehhez viszont már szükségünk van a mockito-ra, melyhez a függőséget a pom.xml-ben így adhatjuk meg:

1
2
3
4
5
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.5.15</version>
</dependency>

Nézzük az egyszerű mockot:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class UtilsTest {

    @Test
    public void simpleMockUtils(){
        Utils utils = mock(Utils.class);

        List<Book> expected = Arrays.asList(new Book("Effective Java", "Joshua Bloch", LocalDate.of(2008, 5, 8)));
        when(utils.importBooksFromCSV(any(Path.class))).thenReturn(expected);

        System.out.println(utils.importBooksFromCSV(Path.of("resources", "books.csv")));
    }

}

Elsőként elkészítjük a Utils osztály egy mock objektumát a mock() metódus meghívásával. Ezután a when()-el specifikáljuk az elvárásokat, azaz egy stub-ot készítünk, vagyis azt mondjuk, hogy mindegy mi van, ha meghívják az importBooksFromCSV metódust bármilyen Path típusú paraméterrel, akkor adja vissza a fent megadott egy könyvből álló listát. Ezután, hogy teszteljük is ennek működését, hívjuk meg ezt a metódust egy tetszőleges Path-al, majd írjuk ki az eredményt a konzolra!

Az egyszerű kiíratás helyett használjunk assert-et!

1
assertThat(utils.importBooksFromCSV(Path.of("resources", "books.csv"))).isEqualTo(expected);

Most teszteljünk kivételkezelést!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Test
public void shouldThrowExceptionForParseBook(){
    Utils utils = mock(Utils.class);

    when(utils.parseBook(any(String.class))).thenThrow(RuntimeException.class);

    assertThrows(RuntimeException.class, () -> {
        utils.parseBook("");
    });
}

A mockolással megadjuk, hogy amikor a parseBook-ot meghívjuk, akkor dobjunk RuntimeException-t, majd ezt teszteljük is egy assertThrows-al.

Egy tipikus mockolási példa kialakításához hozzunk létre egy service réteget, mely belül egy BookShelf-re tárol referenciát!

1
2
3
public boolean containsBookWithTitle(String title){
    return bookShelf.findBookByTitle(title) != null;
}

Nyilván a BookShelf-en a következő metódusra is szükségünk lesz:

1
2
3
public Book findBookByTitle(String title) {
    return books.stream().filter(book -> book.getTitle().contains(title)).findFirst().orElse(null);
}

Nézzük a tesztet!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class BookshelfServiceTest {

    @Test
    public void testFindingBookByTitle(){
        BookShelf bookShelf = mock(BookShelf.class);
        BookshelfService service = new BookshelfService(bookShelf);

        service.containsBookWithTitle("Something");

        verify(bookShelf).findBookByTitle(anyString());
    }

}

A fentiekkel azt teszteljük, hogy a bookshelf findBookByTitle() metódusa meghívódik-e (tetszőleges stringel), melyhez nyilván meg is kell azt hívnunk.

@Mock

Amikor egy teszt osztályon belül többször használjuk a mock()-ot ugyanarra az osztályra, akkor érdemes lehet a @Mock-ot használni!

1
2
@Mock
private BookShelf bookShelf;

Ezután futtatva a tesztet, az elszáll nullpointer exception-nel. A mock objektumokat inicializálnunk kell! Az inicializálást kirakhatjuk egy @BeforeEach-be:

1
2
3
4
@BeforeEach
public void setup(){
    MockitoAnnotations.openMocks(this);
}

@InjectMocks

Automatikus dependency injection során igen hasznos lehet ez az annotáció. Például a fenti példában a következőt írtuk:

1
BookshelfService service = new BookshelfService(bookShelf);

Ez általában felesleges boilerplate kód. Az átalakítás után:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class BookshelfServiceTest {

    @Mock
    private BookShelf bookShelf;

    @InjectMocks
    private BookshelfService service;

    @BeforeEach
    public void setup(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testFindingBookByTitle(){
        service.containsBookWithTitle("Something");
        verify(bookShelf).findBookByTitle(anyString());
    }

}

Amennyiben több ugyanolyan típusú mock objektumunk is van, akkor a @Mock-nak adhatunk egy name attribútumot is, mely alapján az injection végbemegy.


Utolsó frissítés: 2020-11-11 15:02:20