Je me suis inspiré de la présentation du coach Patrick GIRY @PatrickGIRY lors d l’agile Testing night à la SG.
Dans ce cas pratique, je reprend le KATA Trip Service. Il s’agit de tester un code legacy avec des dépendances en dur (hard-wired dependencies).
Les sources sont disponibles sur github.
Regardons la classe TripService
public class TripService {
public List<Trip> getTripsByUser(User user) throws UserNotLoggedInException {
List<Trip> tripList = new ArrayList<Trip>();
User loggedUser = UserSession.getInstance().getLoggedUser();
boolean isFriend = false;
if (loggedUser != null) {
for (User friend : user.getFriends()) {
if (friend.equals(loggedUser)) {
isFriend = true;
break;
}
}
if (isFriend) {
tripList = TripDAO.findTripsByUser(user);
}
return tripList;
} else {
throw new UserNotLoggedInException();
}
}
}
On constate 2 dépendences en dur :
User loggedUser = UserSession.getInstance().getLoggedUser();
tripList = TripDAO.findTripsByUser(user);
Afin d’avoir des TU fonctionnels, il faut casser ces dépendences.
public class TripServiceShould {
private static final User UN_LOGGED_USER = null;
private TripService tripService;
@BeforeEach
public void initService() {
tripService = new TripService() ;
}
@Test
public void throw_not_logged_exception_when_user_not_logged() {
assertThatThrownBy(() -> tripService.getTripsByUser(UN_LOGGED_USER)).isInstanceOf(UserNotLoggedInException.class);
}
}
Le premier TU échoue :
java.lang.AssertionError:
Expecting:
<net.dammak.kata.tripservice.exception.CollaboratorCallException: UserSession.getLoggedUser() should not be called in an unit test>
to be an instance of:
<net.dammak.kata.tripservice.exception.UserNotLoggedInException>
but was:
<"net.dammak.kata.tripservice.exception.CollaboratorCallException: UserSession.getLoggedUser() should not be called in an unit test
L’idée est d’extraire la récupération du user dans une autre méthode, en dehors du code métier :
public class TripService {
public List<Trip> getTripsByUser(User user) throws UserNotLoggedInException {
List<Trip> tripList = new ArrayList<Trip>();
// Refactored Method
User loggedUser = getLoggedUser();
boolean isFriend = false;
// Other code
}
protected User getLoggedUser() {
return UserSession.getInstance().getLoggedUser();
}
}
Au niveau du test, il suffit de surcharger cette méthode, dans une classe Testable de service.
public class TripServiceShould {
private static final User UN_LOGGED_USER = null;
private static final User OTHER_USER = new User();
private static class TestableTripService extends TripService {
private User loggedUser;
private TestableTripService(User user) {
this.loggedUser = user ;
}
private static TestableTripService ofUnloggedUser() {
return new TestableTripService(UN_LOGGED_USER) ;
}
@Override
protected User getLoggedUser() {
return loggedUser;
}
}
@Test
public void throw_not_logged_exception_when_user_not_logged() {
assertThatThrownBy(() -> TestableTripService.ofUnloggedUser().getTripsByUser(OTHER_USER)).isInstanceOf(UserNotLoggedInException.class);
}
}
Le TU suivant échoue :
@Test
void return_trip_to_tunis_and_trip_to_sfax_when_logged_user_friend_with_user_having_those_trips() {
var userHavingFriend = new User() ;
userHavingFriend.addTrip(TRIP_TO_SFAX);
userHavingFriend.addTrip(TRIP_TO_TUNIS);
userHavingFriend.addFriend(LOGGED_USER);
List<Trip> trips = TestableTripService.ofLoggedUser().getTripsByUser(userHavingFriend);
assertThat(trips).contains(TRIP_TO_TUNIS, TRIP_TO_SFAX);
}
On applique la même méthode :
public class TripService {
public List<Trip> getTripsByUser(User user) throws UserNotLoggedInException {
// some code
if (isFriend) {
tripList = findTripsByUser(user);
}
return tripList;
} else {
throw new UserNotLoggedInException();
}
}
// Some code
// Refactored Method
protected User getLoggedUser() {
return UserSession.getInstance().getLoggedUser();
}
}
public class TripServiceShould {
// Some code
private static class TestableTripService extends TripService {
// Some code
@Override
protected List<Trip> findTripsByUser(User user) {
return user.trips() ;
}
}
}