W dużym skrócie – Mock domyślnie nie robi nic (jeśli musi coś zwrócić to zwraca wartości null). Spy to oryginalny obiekt, który może być szpiegowany (np. sprawdzanie czy metoda została wywołana). Obydwa pozwalają na to, żeby nadpisać ich metody – taka nadpisana metoda nazywana jest stubem.
Trochę więcej szczegółów
Załóżmy że mamy interfejs:
interface Connection{
void connect();
boolean isConnected();
void disconnect();
From getFrom();
To getTo();
}Czym będzie Mock?
Będzie to obiekt, który dla każdej metody stworzy „najprostszą” implementację. Przez najprostszą rozumie się, że metody nie będą robiły nic, a jeśli będą zwracały jakieś wartości (o ile mają coś zwracać) to będą to domyślne wartości dla typów prostych i wartości null dla obiektów.
Tak mógłby wyglądać przykładowy mock dla interfejsu Connection:
class ConnectionMock implements Connection{
void connect(){}
boolean isConnected(){ return false;}
void disconnect(){}
From getFrom(){ return null; }
To getTo(){ return null; }
}Czym będzie Spy (szpieg)?
Jest to obiekt najczęściej opakowujący obiekt docelowy (w JavaScript da się to zrobić prościej – wykorzystując prototype). Dzięki takiemu opakowaniu możliwe jest szpiegowanie – sprawdzanie czy metoda została wywołana, z jakimi parametrami i tym podobne.
Załóżmy prostą implementację interfejsu Connection:
class ConnectionImpl implements Connection{
private boolean connected = false;
private From from;
private To to;
ConnectionImpl(From from, To to){ this.from = from; this.to = to;}
void connect(){ connected = true; }
boolean isConnected(){ return connected;}
void disconnect(){ connected = false}
From getFrom(){ return from; }
To getTo(){ return to; }
}I teraz szpiegiem będzie klasa, która opakowuje ConnectionImpl i potrafi szpiegować (w tym wypadku zapisuje historię wywołań i posiada metodę checkHowManyTimesHaveBeenCalled pozwalającą na prosty odczyt ilości wywołań):
class ConnectionSpy implements Connection{
private Connection delegate;
private List<MethodCallHistoryEntry> history;
ConnectionSpy(Connection connection){
this.delegate = connection;
}
public long checkHowManyTimesHaveBeenCalled(String methodName){
return history.stream()
.filter(methodCallHistoryEntry ->
methodCallHistoryEntry.getName().equals(methodName))
.count();
}
void connect(){
history.add(MethodCallHistoryEntry.create("connect"));
delegate.connect();
}
boolean isConnected(){
history.add(MethodCallHistoryEntry.create("isConnected"));
delegate.isConnected();
}
void disconnect(){
history.add(MethodCallHistoryEntry.create("disconnect"));
delegate.disconnect();
}
From getFrom(){
history.add(MethodCallHistoryEntry.create("getFrom"));
delegate.getFrom();
}
To getTo(){
history.add(MethodCallHistoryEntry.create("getTo"));
delegate.getTo();
}
}Czym jest stub?
Jest to modyfikacja Mock lub Spy poprzez nadpisanie konkretnej metody (poniższe przykłady nie zawierają implementacji metody stub, na dole znajdziesz link do pełnej implementacji).
Przykład dla Mock (stub na metodach getFrom i getTo):
ConnectionMock mock = new ConnectionMock();
mock.stub("getFrom", (a) -> new From("127.0.0.0:80"));
if(mock.getFrom() != null) {
System.out.println("Successfully applied stub on ConnectionMock");
}Przykład dla Spy (stub na metodzie connect):
ConnectionImpl connection = new ConnectionImpl(new From("127.0.0.0:80"), new To("127.0.0.0:81"));
ConnectionSpy connectionSpy = new ConnectionSpy(connection);
connectionSpy.connect(); connectionSpy.stub("disconnect", (a) -> null);
connectionSpy.disconnect();
if(connectionSpy.isConnected()){
System.out.println("Successfully applied stub on disconnect method");
}Pełny kod, posiadający metodę stub możesz znaleźć tutaj – https://github.com/damianradowiecki/spy-vs-mock
