如何使用 JUnit 在 Spring Boot 中的公共方法中模拟私有方法

Posted

技术标签:

【中文标题】如何使用 JUnit 在 Spring Boot 中的公共方法中模拟私有方法【英文标题】:How to mock private method in public method in Spring Boot with JUnit 【发布时间】:2019-01-09 11:04:21 【问题描述】:

我想问你几个问题并征求你的意见:

我想测试我的公共方法(我使用 Spring Boot、Mockito、JUnit):

@Service
public class MyClass

public Long getClientId(List<String> nameSurname) throws AuthorizationException 
        Long operatorId;
        if(...)
        (... something not interesting ...)
            User user = getUserByLogin("AnthonyGates2");
            operatorId = nonNull(user) ? user.getOperatorId() : null;
         else 
            List<User> users = getUserListByLogin("AnthinyGates");
            operatorId = isNotEmpty(users) ? return operatorId;
         return operatorId;
    

如何测试方法getClientId

方法 getUserByLogingetUserListByLogin 在此类 (MyClass) 中是私有的,但我必须模拟这些私有方法的结果,因为这些方法从外部服务检索数据。 这些私有方法看起来像:

User user = DelegateImpl.getDelegate().getUserByLogin(nameAndSurname);

DelegateImpl.getDelegate().getUserByLogin 从数据库中获取数据,并且必须像这样模拟数据:

when(DelegateImpl.getDelegate().getUserByLogin(any())).thenReturn(user);

如何测试我的公开课?我应该使用 PowerMock/PowerMockito 吗?在我看来,公开这些方法很难看,因为这些方法只在MyClass 中调用。我在 Internet 上找不到适合我的案例的好的教程(Spring Boot、Mockito、JUnit)。

非常感谢您的所有提示!

最好的问候 马修

【问题讨论】:

不要模拟你的私有方法,模拟他们使用的外部服务。 【参考方案1】:

仅通过调用公共方法来测试单元。我认为您的示例是服务层中的一个类(包含业务逻辑),两个 getUser... 方法应该在不同的类中(我认为在数据层中),它们可以是公共的。通过构造函数将该类作为依赖项(在服务对象中)注入,以便在测试服务类时模拟它。数据层类(使用 getUser... 方法)也可以通过它自己的单元测试进行测试。

【讨论】:

MyClass 是 MyClassService 并且具有与我的两个私有方法(调用外部服务)非常相似的更多公共单行方法,例如 public void saveUser(User user) DelegateImpl.getDelegate().saveUser(用户); 或者我应该保护这些方法吗?然后使用 Mockito?或者也许最好的选择是将我的私有方法添加到 MyClassService 并将它们更改为 public 并且整个逻辑移动到另一个类,因此在 MyClassService 中应该只有调用外部服务的公共方法...... 如果这两个方法包含业务逻辑,您可以将它们移动到例如 UserService 类,在那里它们可以是公共的,然后您编写一个 UserServiceTest 类。将 UserService 传递给实现 getClientId 的 ClientLoginService(或类似的东西)。如果测试比你的设计不正确(抽象问题和/或静态调用) 是的——多亏了你,我明白当我们不能模拟并且应该测试私有方法时,我们可以重构我们的代码。所以我从 MyClass 中删除了所有逻辑,只留下了调用外部服务的公共方法。我将我的公共方法 getClientId 移动到另一个使用 MyClass 中的公共方法的类。这样我就可以轻松使用 Mockito。谢谢大家的帮助! 很高兴听到重构成功的消息! :)【参考方案2】:

如果你不能对一个方法/类进行单元测试,那很可能意味着它做的太多了。尝试将您的私有方法提取到单独的类中。它不需要公开 - 例如,您可以将其本地包放在同一个包中。

稍后,在测试中,您必须注入该类的模拟并模拟其行为。

MyClass 在其单元测试中的设置可能类似于:

AnotherClass anotherClassMock = Mockito.mock(AnotherClass.class);
MyClass myClass = new MyClass(anotherClassMock);

AnotherClass 将有方法 getUserListByLogin 和 getUserByLogin。

编辑: 您的私有方法中的逻辑似乎已经调用了外部类。问题是您通过调用另一个类中的静态getDelegate() 方法来获取对象的实例。

您可以这样做:

MyClass 中创建一个新字段,该字段的类型与getDelegate() 方法返回的类型相同(我不知道那是什么,我称之为Delegate) 有 2 个构造函数:一个默认构造函数将 getDelegate 方法的结果分配给您的新字段,另一个将 Delegate 的实例作为参数并将其分配给您的字段 在测试中使用第二个构造函数创建MyClass 的实例并传递Delegate 类的模拟

看起来更像是这样:

class MyClass() 
    private Delegate delegate;

    MyClass() 
        this.delegate = DelegateImpl.getDelegate();
    

    MyClass(Delegate delegate) 
        this.delegate = delegate;
    
    // ... the rest

【讨论】:

我的 2 个私有方法非常小,它们看起来像: return DelegateImpl.getDelegate().getUserByLogin(nameAndSurname);仅此而已。 我用另一个命题编辑了我的答案。请,看看它是否有帮助。 不要调用静态方法来解决其他依赖关系(DelegateImpl.getDelegate() 是静态调用)。这使得测试非常困难。通过构造函数使用依赖注入。

以上是关于如何使用 JUnit 在 Spring Boot 中的公共方法中模拟私有方法的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JUnit 在 Spring Boot 中的公共方法中模拟私有方法

如何使用 JUnit 测试 Spring Boot aspectj 方法?

如何 JUnit 测试 Spring-Boot 的 Application.java

使用 JUnit 5 的 spring-boot-starter-test

如何在 Spring Boot 和 JUnit 测试中定义 spring.config.location?

如何在单个 JUnit 测试中运行两个 Spring Boot 微服务?