【问题标题】:NullPointerException from mocked bean in a constructor - How can I correctly mock a mockBean from constructor by customizing it first?来自构造函数中的模拟 bean 的 NullPointerException - 如何通过首先自定义构造函数来正确模拟构造函数中的 mockBean?
【发布时间】:2019-01-10 22:54:17
【问题描述】:

我在我的测试类中为 WebClient.Builder 实例使用 @MockBean 注释,当我尝试设置 SSL 上下文时,它会导致 NullPointerException。

我不知道为什么当我没有尝试设置上下文并只是调用 build() api 时没有看到错误,如下所示:

服务类 v1:

@Service
public class ABCD {

private static final Logger logger = LoggerFactory.getLogger(ABCD.class);

private String apiUrl;

private final WebClient webClient;
private final XYZRepository repository;

public ABCD(WebClient.Builder webClientBuilder,
                   XYZRepository repository, @Value("${api-root-url}") String apiUrl) {

    //------------------------
    this.webClient = webClientBuilder.build();
    //------------------------
    this.repository = repository;
    this.apiUrl = apiUrl;
    }
}

服务类 v2:

@Service
public class ABCD {

private static final Logger logger = LoggerFactory.getLogger(ABCD.class);

private String apiUrl;

private final WebClient webClient;
private final XYZRepository repository;

public ABCD(WebClient.Builder webClientBuilder,
                   XYZRepository repository, @Value("${api-root-url}") String apiUrl) throws SSLException {

    SslContext sslContext = SslContextBuilder
            .forClient()
            .trustManager(InsecureTrustManagerFactory.INSTANCE)
            .build();
    ClientHttpConnector httpConnector = new ReactorClientHttpConnector(options -> options.sslContext(sslContext));

    //------------------------
    this.webClient = webClientBuilder
            .clientConnector(httpConnector)
            .build();
    //------------------------
    this.repository = repository;
    this.apiUrl = apiUrl;
    }
}

测试类:

@RunWith(SpringRunner.class)
@WebFluxTest(ABCD.class)
public class ABCDTest {

    @MockBean
    XYZRepository repository;

    @MockBean
    WebClient.Builder webClientBuilder;

    @SpyBean
    ABCD ABCDService;


    WebClient webClient;

    @Value("${api-root-url}")
    String apiRootUrl;

    @Before
     public void setup() {
         this.objMapper = new ObjectMapper();
         this.mockWebServer = new MockWebServer();
         String baseUrl = this.mockWebServer.url("/").toString();
         this.webClient = WebClient.create(baseUrl);

         MockitoAnnotations.initMocks(this);
         ReflectionTestUtils.setField(ABCDService,
                 "apiRootUrl", API_ROOT_URL);
         ReflectionTestUtils.setField(ABCDService,
                 "webClient", this.webClient);
     }

    }

在 v2 中

this.webClient = webClientBuilder .clientConnector(httpConnector) .build();

build() 调用中导致 NPE。如何在不引起 NPE 的情况下模拟 webclient?

我试过了,在 setup() 方法中添加以下代码来模拟 clientConnector 方法的响应:

when(this.webClientBuilder.clientConnector(any()))
                .thenReturn(this.webClientBuilder);

所以,我很好奇在哪里可以添加这段代码来使用上面的模拟。

【问题讨论】:

    标签: java spring spring-boot mocking mockito


    【解决方案1】:

    我认为 webClientBuilder 已创建为模拟(它不是空的 - 是吗?),但您还没有告诉模拟在调用 clientConnector 方法时要做什么。因此,Mockito 的默认行为是返回 null - 因此当您在该响应上调用 build() 时您的 NPE。

    因此,您需要 webClientBuilderclientConnector 方法返回自身,并从 build() 返回 webClient。通过添加以下行来做到这一点:

    when(webClientBuilder.clientConnector(any(ClientHttpConnector.class))).thenReturn(webClientBuilder);
    when(webClientBuilder.build()).thenReturn(webClient);
    

    【讨论】:

    • 同意,我之前曾尝试使用完全相同的代码进行模拟。但是我一直无法找到合适的地方来放置该代码。我应该在问题中提到这一点。我尝试将它添加到 setup() 方法,但这显然是晚了,因为 ABCD 的构造函数调用是在该方法之前进行的。
    • 一些想法:1)来自docs.spring.io/spring-boot/docs/current/api/org/springframework/… 看起来应该将 \@SpyBean 应用于您的模拟,而不是正在测试的 ABCDservice(使用 \@Autowired ?)。 2)你的构造函数做的太多了——它应该只是设置实例变量。尝试将 WebClient 的初始化移动到 \@PostConstruct 方法 - 为您提供更多选项(例如,使用 MockitoJUnitRunner,如 stackoverflow.com/questions/42112625/…
    猜你喜欢
    • 2019-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 2012-01-28
    • 1970-01-01
    相关资源
    最近更新 更多