Test Double: a Spy
Problem
A NotificationService must send a message through a Notifier, unless the user is muted. In a test, you do not want to send real messages, so you give the service a spy: a Notifier that only records the calls. Commands: mute user, unmute user, notify user message (the message is the rest of the line and can have spaces; no output) and report. report prints calls=N, where N is the number of messages that reached the notifier so far, followed by one line per message: user: message.
Constraints
- 1 <= commands <= 10^4
Examples
Input
notify amit hi mute amit notify amit no report
Output
calls=1 amit: hi
Solutions
A spy records how it was used, so a test can check the behaviour afterwards: how many calls, with which values. A stub only returns fixed answers; a mock also checks the expectations itself; a fake is a light working copy (an in-memory database). Mockito (Java), Jest (JavaScript) and unittest.mock (Python) create these for you.
/*
* Problem : Process lines.
* Input : Several lines.
* Output : Spy reports.
* Approach: Service with a spy.
* Time : O(n)
* Space : O(n)
* Java : 8 or newer.
*/
import java.util.Scanner;
public class Main {
// Step 1: The service depends on this interface. A real notifier would send an e-mail or an SMS.
interface Notifier {
void send(String user, String message);
}
// Step 2: The spy is a test double. It sends nothing and only records every call.
static class SpyNotifier implements Notifier {
final java.util.List<String> calls = new java.util.ArrayList<>();
@Override
public void send(String user, String message) {
calls.add(user + ": " + message);
}
}
// Step 3: The service holds the rule "muted users get nothing". It does not know which notifier it has.
static class NotificationService {
private final Notifier notifier;
private final java.util.Set<String> muted = new java.util.HashSet<>();
NotificationService(Notifier notifier) {
this.notifier = notifier;
}
void mute(String user) {
muted.add(user);
}
void unmute(String user) {
muted.remove(user);
}
void notify(String user, String message) {
if (!muted.contains(user)) {
notifier.send(user, message);
}
}
}
// This method solves the task. It receives the input and returns the answer.
static String[] solve(String[] lines) {
SpyNotifier spy = new SpyNotifier();
NotificationService service = new NotificationService(spy);
java.util.List<String> output = new java.util.ArrayList<>();
for (String line : lines) {
if (line.trim().isEmpty()) {
continue;
}
String[] part = line.split(" ", 3);
if (part[0].equals("mute")) {
service.mute(part[1]);
} else if (part[0].equals("unmute")) {
service.unmute(part[1]);
} else if (part[0].equals("notify")) {
service.notify(part[1], part[2]);
} else {
// Step 4: The "test" asks the spy what happened.
output.add("calls=" + spy.calls.size());
output.addAll(spy.calls);
}
}
return output.toArray(new String[0]);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
java.util.List<String> all = new java.util.ArrayList<>();
while (scanner.hasNextLine()) {
all.add(scanner.nextLine());
}
String[] lines = all.toArray(new String[0]);
System.out.println(String.join("\n", solve(lines)));
}
}
Hints (1)
- What is the difference between a stub and a spy?