Το Mockito είναι ένα framework ανοιχτού κώδικα για τη δημιουργία mock-αντικειμένων σε unit tests Java και Kotlin, το οποίο επιτρέπει την απομόνωση του υπό δοκιμή κώδικα από εξωτερικές εξαρτήσεις. Με τη βοήθειά του, ο προγραμματιστής αντικαθιστά πραγματικά repositories, API clients και βάσεις δεδομένων με ελεγχόμενα υποκατάστατα με καθορισμένη συμπεριφορά. Σύμφωνα με τα δεδομένα του Mockito.org, η βιβλιοθήκη χρησιμοποιείται σε περισσότερο από 60% των Java έργων που εφαρμόζουν unit testing.
Κύρια σημεία
Το Mockito είναι μια βιβλιοθήκη ανοιχτού κώδικα για τη δημιουργία mock-αντικειμένων (υποκατάστατων) σε unit tests σε Java, Kotlin και άλλες γλώσσες JVM. Σε αντίθεση με το JUnit, που είναι υπεύθυνο για την εκτέλεση των tests, το Mockito λύνει το πρόβλημα της απομόνωσης — αντικαθιστά τις πραγματικές εξαρτήσεις της υπό δοκιμή κλάσης με προβλέψιμα αντικείμενα.
Χωρίς mocks, η δοκιμή μιας μεθόδου που έχει πρόσβαση σε βάση δεδομένων ή εξωτερικό API απαιτεί ρύθμιση πραγματικού περιβάλλοντος — ανάπτυξη βάσης δεδομένων, εκκίνηση διακομιστή. Το Mockito αντικαθιστά αυτές τις εξαρτήσεις με αντικείμενα σταθερής συμπεριφοράς: η μέθοδος repository.findById(1) επιστρέφει πάντα ένα συγκεκριμένο αντικείμενο User, χωρίς να προσπελαύνει τη βάση δεδομένων.
Η αρχιτεκτονική του Mockito βασίζεται στο μοτίβο Proxy (για interfaces και κλάσεις). Η βιβλιοθήκη δημιουργεί μια υποκλάση ή proxy για τον καθορισμένο τύπο και παρεμποδίζει όλες τις κλήσεις μεθόδων, επιστρέφοντας προεπιλεγμένες τιμές ή τιμές που ορίζονται μέσω του when().thenReturn().
Η αρχή λειτουργίας του Mockito βασίζεται σε τρεις βασικές λειτουργίες: δημιουργία mock, ρύθμιση συμπεριφοράς (stubbing) και επαλήθευση κλήσεων (verification). Κάθε λειτουργία χρησιμοποιεί στατικές μεθόδους από την κλάση org.mockito.Mockito — την πιο φορτωμένη κλάση στο οικοσύστημα Java σύμφωνα με στατιστικά του Maven Central. Όλες οι κλήσεις μεθόδων του mock καταγράφονται στη μνήμη, επιτρέποντας τον μετέπειτα έλεγχό τους μέσω verify.
Ένα τυπικό test με Mockito αποτελείται από τρεις φάσεις: Arrange — δημιουργία mocks και ρύθμιση stubs μέσω when().thenReturn(), Act — κλήση της υπό δοκιμή μεθόδου, Assert — έλεγχος αποτελέσματος μέσω assertEquals και verify(mock). Αυτή η προσέγγιση ονομάζεται AAA (Arrange-Act-Assert).
Ας εξετάσουμε ένα απλό test όπου το Mockito αντικαθιστά το repository χρηστών. Η μέθοδος when().thenReturn() ρυθμίζει το mock έτσι ώστε η κλήση findById να επιστρέφει ένα προετοιμασμένο αντικείμενο User.
// Δημιουργούμε mock του repository
UserRepository mockRepo = mock(UserRepository.class);
// Ρυθμίζουμε συμπεριφορά: στο findById(1) επέστρεψε τον χρήστη
when(mockRepo.findById(1)).thenReturn(new User("Alice"));
// Ελέγχουμε ότι η μέθοδος κλήθηκε πραγματικά
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);
Το Mockito παρέχει δύο τρόπους δημιουργίας mocks: τη στατική μέθοδο mock(Class) και το annotation @Mock με αρχικοποίηση μέσω MockitoAnnotations.openMocks(). Ο πρώτος τρόπος είναι συμπαγής για ένα-δύο mocks, ο δεύτερος είναι βολικός όταν υπάρχουν πολλές εξαρτήσεις — τα annotations μειώνουν τον boilerplate κώδικα.
Η μέθοδος mock() δέχεται μια κλάση και επιστρέφει ένα υποκατάστατο αντικείμενο που μπορεί να ρυθμιστεί μέσω when().thenReturn(). Όλες οι μη ρυθμισμένες μέθοδοι επιστρέφουν προεπιλεγμένες τιμές: 0 για αριθμούς, false για boolean, null για αντικείμενα.
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
Το annotation @Mock σε συνδυασμό με @ExtendWith(MockitoExtension.class) δημιουργεί αυτόματα mocks για όλα τα πεδία της κλάσης test. Η επέκταση MockitoExtension είναι υπεύθυνη για την αρχικοποίηση πριν από κάθε test.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void getUserShouldReturnUserFromRepo() {
when(userRepository.findById(1)).thenReturn(new User("Alice"));
User result = userService.getUser(1);
assertEquals("Alice", result.getName());
}
}
Stubbing — η διαδικασία καθορισμού του τι πρέπει να επιστρέψει η μέθοδος mock όταν καλείται με συγκεκριμένα ορίσματα. Βασική σύνταξη: when(mock.method(args)).thenReturn(value). Για διαφορετικά σενάρια, το Mockito προσφέρει αρκετές παραλλαγές μεθόδων then.
| Μέθοδος | Σκοπός |
|---|---|
| thenReturn(value) | Επιστρέφει πάντα την καθορισμένη τιμή |
| thenThrow(exception) | Πετάει exception κατά την κλήση |
| thenAnswer(answer) | Υπολογίζει δυναμικά την επιστρεφόμενη τιμή |
| thenCallRealMethod() | Καλεί την πραγματική μέθοδο (μερικό mock) |
Όταν η επιστρεφόμενη τιμή εξαρτάται από τα ορίσματα κλήσης, χρησιμοποιείται το thenAnswer με lambda. Αυτό είναι χρήσιμο για προσομοίωση εργασίας με πραγματικά δεδομένα — για παράδειγμα, δημιουργία ID βάσει του μεταβιβαζόμενου αντικειμένου.
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Το Verify είναι μια μοναδική δυνατότητα του Mockito που δεν παρέχουν οι παλαιότερες βιβλιοθήκες mock-αντικειμένων (EasyMock, jMock).
Το Verify κάνει τα tests πιο αξιόπιστα, καθώς ελέγχει όχι μόνο την επιστρεφόμενη τιμή, αλλά και τις παρενέργειες — κλήσεις μεθόδων που δεν επιστρέφουν αποτέλεσμα (void μέθοδοι). Η μέθοδος verify(mock).methodName(args) ελέγχει αν μια συγκεκριμένη μέθοδος του mock κλήθηκε με τα καθορισμένα ορίσματα. Αυτό επιτρέπει τη δοκιμή όχι μόνο του αποτελέσματος, αλλά και της διαδικασίας — του γεγονότος πρόσβασης στην εξάρτηση.
Από προεπιλογή, το verify ελέγχει ότι η μέθοδος κλήθηκε ακριβώς μία φορά. Αν χρειάζεται διαφορετικός αριθμός — χρησιμοποιούνται τα times(n), atLeast(n), never() και άλλοι τροποποιητές από την κλάση Mockito.
// Έλεγχος αριθμού κλήσεων
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);
// Έλεγχος σειράς κλήσεων
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();
Όταν πρέπει να ελεγχθεί με ποιο ακριβώς αντικείμενο κλήθηκε η μέθοδος, χρησιμοποιείται το ArgumentCaptor. Αυτό καταγράφει την τιμή του ορίσματος κατά την κλήση και επιτρέπει τον ξεχωριστό έλεγχο των πεδίων του. Το ArgumentCaptor είναι ιδιαίτερα χρήσιμο όταν ο υπό δοκιμή κώδικας δημιουργεί ένα αντικείμενο εσωτερικά και το μεταβιβάζει στην εξάρτηση — δεν μπορείτε να ελέγξετε αυτό το αντικείμενο με άλλον τρόπο.
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock και @InjectMocks — δύο βασικά annotations του Mockito που μειώνουν σημαντικά τον boilerplate κώδικα. Το @Mock δημιουργεί ένα mock για ένα πεδίο, και το @InjectMasks εισάγει όλα τα mocks από την κλάση test στο υπό δοκιμή αντικείμενο μέσω κατασκευαστή, setter ή πεδίου.
Ο μηχανισμός @InjectMocks προσπαθεί να εισαγάγει εξαρτήσεις με την ακόλουθη σειρά: κατασκευαστής με τον μεγαλύτερο αριθμό ορισμάτων, setter ανά τύπο, ιδιωτικό πεδίο. Αν καμία μέθοδος δεν λειτουργήσει — το αντικείμενο παραμένει με null εξαρτήσεις και το test αποτυγχάνει με NullPointerException.
Είναι σημαντικό να κατανοήσετε: το @InjectMocks δεν αναλύει τους τύπους πεδίων — εισάγει οποιοδήποτε mock είναι συμβατό με τον τύπο. Αν η κλάση έχει δύο πεδία του ίδιου τύπου — το Mockito μπορεί να εισαγάγει λάθος mock. Σε τέτοιες περιπτώσεις, συνιστάται η χρήση ρητού κατασκευαστή με μεταβίβαση των mocks.
Στην ανάπτυξη Android, το Mockito χρησιμοποιείται μαζί με το JUnit για τη δοκιμή ViewModel, Repository και UseCase. Δεδομένου ότι αυτές οι κλάσεις εκτελούνται σε JVM χωρίς περιβάλλον Android, το Mockito αντικαθιστά τις εξαρτήσεις τους — Room DAO, Retrofit API, SharedPreferences — με stubs προβλέψιμης συμπεριφοράς.
Για να προσθέσετε το Mockito σε ένα Android έργο, αρκεί να προσθέσετε την εξάρτηση mockito-core ή mockito-inline (η τελευταία υποστηρίζει mocking τελικών κλάσεων και στατικών μεθόδων). Η έκδοση 5.12.0 (2024) περιλαμβάνει υποστήριξη για Java 21 και βελτιωμένη ενσωμάτωση με JUnit 5.
// build.gradle.kts (module)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
Παλαιότερα, για το mocking στατικών μεθόδων και κατασκευαστών απαιτείτο το PowerMock — μια επέκταση που λειτουργούσε μέσω bytecode instrumentation. Από το Mockito 5.x με mockito-inline, αυτή η λειτουργικότητα είναι ενσωματωμένη απευθείας: το mockStatic(ClassName.class) επιτρέπει το mocking στατικών μεθόδων χωρίς πρόσθετες βιβλιοθήκες.
Τυπικό σενάριο: το ViewModel καλεί μια μέθοδο του repository και μετασχηματίζει το αποτέλεσμα σε κατάσταση UI. Το Mockito αντικαθιστά το repository και το test ελέγχει ότι το ViewModel επεξεργάζεται σωστά την επιτυχημένη απόκριση και το σφάλμα. Κατά τη χρήση της Clean Architecture, δημιουργούνται mocks για κάθε επίπεδο: DataSource, Repository και UseCase — αυτό επιτρέπει τη δοκιμή κάθε επιπέδου απομονωμένα.
Συχνές Ερωτήσεις
Mockito είναι μια βιβλιοθήκη για Java και Kotlin που χρησιμοποιεί proxy και reflection. Το MockK είναι μια Kotlin-first βιβλιοθήκη με υποστήριξη για coroutines, extension functions και τελικές κλάσεις χωρίς πρόσθετη ρύθμιση.
Από το Mockito 2.1, το mocking τελικών κλάσεων υποστηρίζεται μέσω opt-in. Στην έκδοση 5.x (mockito-inline) αυτό είναι ενεργοποιημένο από προεπιλογή. Αρκεί να προσθέσετε την εξάρτηση mockito-inline και να χρησιμοποιήσετε την τυπική μέθοδο mock().
Spy είναι ένα μερικό mock που από προεπιλογή καλεί πραγματικές μεθόδους, αλλά επιτρέπει την παράκαμψη ορισμένων από αυτές μέσω when().thenReturn(). Το Spy είναι χρήσιμο για δοκιμή παλαιού κώδικα όταν δεν μπορεί να ξαναγραφεί ολόκληρη η κλάση.
thenReturn επιστρέφει πάντα την ίδια τιμή, ανεξάρτητα από τα ορίσματα. thenAnswer υπολογίζει την επιστρεφόμενη τιμή με βάση την κλήση — ορίσματα, το ίδιο το mock, κατάσταση. Για δυναμικές απαντήσεις, χρησιμοποιείτε πάντα το thenAnswer.
Verify ελέγχει όχι μόνο το αποτέλεσμα, αλλά και τη διαδικασία — το γεγονός πρόσβασης στην εξάρτηση. Αυτό είναι κρίσιμο για υπηρεσίες που πρέπει να αποθηκεύουν δεδομένα ή να στέλνουν ειδοποιήσεις. Χωρίς verify, το test δεν θα ανιχνεύσει ότι η μέθοδος δεν κάλεσε save() ή send().
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης