如何为Java字符串相等校验的菜单项更新逻辑编写JUnit单元测试?
要给这个方法写单元测试,核心是隔离外部依赖——你的代码直接依赖了Scanner(控制台输入)和menuPrinter,必须用Mock对象替换这些依赖,避免测试依赖真实控制台交互。以下是具体步骤和测试用例:
第一步:重构代码以支持测试
先把Scanner和menuPrinter通过构造方法注入,这样测试时可以替换成Mock实例:
// 假设你的类名为MenuService public class MenuService { private final Scanner scanner; private final MenuPrinter menuPrinter; // 构造方法注入依赖 public MenuService(Scanner scanner, MenuPrinter menuPrinter) { this.scanner = scanner; this.menuPrinter = menuPrinter; } @Override public void updateMenu(DailyMenu dailyMenu) { // 保持原方法逻辑不变 } }
第二步:用Mockito编写测试用例
Mockito是Java生态最常用的Mock框架,用来模拟依赖、控制输入输出、验证方法调用。
测试场景1:匹配到菜单项并成功更新
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.ArrayList; import java.util.List; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class MenuServiceTest { @Mock private Scanner scanner; @Mock private MenuPrinter menuPrinter; @Test public void updateMenu_whenItemExists_updatesMenuItem() { // 1. 准备测试数据 MenuItem targetItem = new MenuItem("OldName", "OldDesc", "OldImg", 10.0f); List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(targetItem); menuItems.add(new MenuItem("OtherItem", "Desc", "Img", 20.0f)); DailyMenu dailyMenu = new DailyMenu(); dailyMenu.setMenuItemList(menuItems); // 2. 模拟用户输入流程:旧名称→新名称→新描述→新图片→新价格 when(scanner.nextLine()).thenReturn("OldName", "NewName", "NewDesc", "NewImg"); when(scanner.nextFloat()).thenReturn(15.0f); // 消费nextFloat后的换行符,避免后续输入异常 doNothing().when(scanner).nextLine(); // 3. 执行测试方法 MenuService service = new MenuService(scanner, menuPrinter); service.updateMenu(dailyMenu); // 4. 验证菜单项属性是否更新 assert targetItem.getNames().equals("NewName"); assert targetItem.getDescription().equals("NewDesc"); assert targetItem.getImage().equals("NewImg"); assert targetItem.getPrice() == 15.0f; // 5. 验证依赖方法是否按预期调用 verify(menuPrinter).printMenu(dailyMenu); verify(scanner, times(4)).nextLine(); verify(scanner).nextFloat(); } }
测试场景2:未匹配到菜单项,无任何修改
@Test public void updateMenu_whenItemDoesNotExist_noChanges() { // 1. 准备测试数据 MenuItem existingItem = new MenuItem("Existing", "Desc", "Img", 10.0f); List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(existingItem); DailyMenu dailyMenu = new DailyMenu(); dailyMenu.setMenuItemList(menuItems); // 2. 模拟输入不存在的菜单项名称 when(scanner.nextLine()).thenReturn("NonExistentItem"); // 3. 执行测试方法 MenuService service = new MenuService(scanner, menuPrinter); service.updateMenu(dailyMenu); // 4. 验证菜单项属性未被修改 assert existingItem.getNames().equals("Existing"); assert existingItem.getPrice() == 10.0f; // 5. 验证依赖调用 verify(menuPrinter).printMenu(dailyMenu); verify(scanner).nextLine(); verifyNoMoreInteractions(scanner); }
测试场景3:价格输入非法,触发异常处理
@Test public void updateMenu_whenPriceIsInvalid_handlesException() { // 1. 准备测试数据 MenuItem targetItem = new MenuItem("OldName", "OldDesc", "OldImg", 10.0f); List<MenuItem> menuItems = new ArrayList<>(); menuItems.add(targetItem); DailyMenu dailyMenu = new DailyMenu(); dailyMenu.setMenuItemList(menuItems); // 2. 模拟输入:旧名称→新属性→非法价格 when(scanner.nextLine()).thenReturn("OldName", "NewName", "NewDesc", "NewImg"); when(scanner.nextFloat()).thenThrow(new InputMismatchException()); // 3. 执行测试方法 MenuService service = new MenuService(scanner, menuPrinter); service.updateMenu(dailyMenu); // 4. 验证异常被捕获,菜单项未被修改 assert targetItem.getNames().equals("OldName"); }
核心注意事项
- 依赖注入是前提:必须把
Scanner和menuPrinter通过构造方法传入,不能在方法内直接实例化,否则无法替换为Mock对象。 - 模拟输入要完整:严格按照用户操作流程模拟
Scanner的每一次输入,包括处理nextFloat()后的换行符。 - 双重验证:既要验证业务结果(菜单项属性),也要验证依赖行为(比如
menuPrinter是否调用、Scanner方法调用次数)。
内容的提问来源于stack exchange,提问作者David Moore
相关产品推荐
相关产品推荐

