SpringBoot RestController 的 Spock 测试和异常模拟

Spock testing for SpringBoot RestController and mocking of exceptions

我不熟悉 groovy 为 spring 启动应用程序测试 RestController 的方法。我有一个控制器 class :

@RestController
@RequestMapping(value = "/onboarding/v1")
public class OnboardingController {
  private static final Logger LOG = LoggerFactory.getLogger(OnboardingServiceImpl.class);

  @Autowired
  private OnboardingService onboardingService;

  @RequestMapping(
    value = "/service-models",
    method = RequestMethod.POST,
    consumes = { "multipart/form-data" },
    produces = { "application/json" }
  )
  public ResponseEntity createServiceModel(
    @RequestParam("name") final String name,
    @RequestParam("file") final MultipartFile file
  ){
    try {
      final ServiceModelRequestData serviceModelRequestData =
        new ServiceModelRequestData(name, file);
      final ServiceModelDetail createdServiceModel =
        onboardingService.createServiceModel(serviceModelRequestData);
      return new ResponseEntity<>(createdServiceModel, HttpStatus.OK);
    }
    catch (MalformedContentException ex) {
      LOG.error("Malformed Content:", ex);
      return new ResponseEntity<>(errorMessage(ex), HttpStatus.BAD_REQUEST);
    }
    catch (ServiceModelNameAlreadyExistsException ex) {
      LOG.error("Service Model Name Already Exists:", ex);
      return new ResponseEntity<>(errorMessage(ex), HttpStatus.CONFLICT);
    }
    catch (ServiceUnavailableException ex) {
      LOG.error("Service Unavailable currently:" + ex);
      return new ResponseEntity<>(errorMessage(ex), HttpStatus.SERVICE_UNAVAILABLE);
    }
  }
  //...
}

我无法找到如何对上述 class 进行 spock 测试,同时编写异常的 spock 测试用例并检查所需的响应?我想创建一个测试用例,它在方法执行时抛出错误被调用并且 returns 我要检查的响应实体包含给定的 Jason 数据以及所需的 Http 状态。代码片段的示例 spock 测试将不胜感激。

已编辑: 测试 Class 如下(以及我尝试做的事情的评论):

package com.service.onboarding.web.controller

import spock.lang.Specification

import com.service.onboarding.business.OnboardingServiceImpl
import com.service.onboarding.business.api.OnboardingService
import com.service.onboarding.domain.exception.MalformedContentException
import com.service.onboarding.domain.exception.ServiceModelNameAlreadyExistsException
import com.service.onboarding.domain.exception.ServiceUnavailableException
import com.service.onboarding.domain.resource.Greeting

import groovy.json.internal.Exceptions

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.web.multipart.MultipartFile
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.ResponseEntity
import spock.lang.Unroll
import static org.mockito.BDDMockito.given



/*@WebMvcTest
 public class OnboardingControllerSpec extends Specification {
 @MockBean
 private OnboardingService onboardingService;
 @Autowired
 private MockMvc mockMvc;
 def "controller should return expected JSON content and OK response"() {
 given: 'hello world service responds with greeting'
 def name = "Emily"
 given(onboardingService.getPersonalGreeting("${name}")).willReturn(new Greeting(1, "Hi, ${name}"));
 when: 'hello world service is called with name provided'
 def response  = mockMvc.perform(get("/onboarding/v1?name=${name}"))
 then: 'expected JSON returned and response code is OK'
 response
 .andExpect(status().isOk())
 .andExpect(content().json("{'id': 1, 'content': 'Hi, ${name}'}"))
 }
 }*/

public class OnboardingControllerSpec extends Specification{
    OnboardingServiceImpl service =new OnboardingServiceImpl()
    OnboardingController controller
    ResponseEntity response

    @Unroll
    def "HTTP response #statusCode when creating service model"() {
        given:
        if (exception) {
            service = Stub() {
                createServiceModel(_) >> { throw exception }
            }
        }

        controller= new OnboardingController(onboardingService: service)

        when:
        response=controller.createServiceModel("test", Mock(MultipartFile))

        then:
        response.statusCode == statusCode

        where:
        exception                                    | statusCode
        null                                         | OK
        new MalformedContentException()              | BAD_REQUEST
        new ServiceModelNameAlreadyExistsException() | CONFLICT
        new ServiceUnavailableException()            | SERVICE_UNAVAILABLE

    }
}

我的服务class如下:

package com.service.onboarding.business;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.service.onboarding.business.api.OnboardingService;
import com.service.onboarding.business.servicemanagement.ServiceModelRepository;
import com.service.onboarding.domain.exception.MalformedContentException;
import com.service.onboarding.domain.exception.ServiceModelDoesNotExistException;
import com.service.onboarding.domain.exception.ServiceModelInUseException;
import com.service.onboarding.domain.exception.ServiceModelNameAlreadyExistsException;
import com.onboarding.domain.exception.ServiceUnavailableException;
import com.service.onboarding.domain.requestdata.ServiceModelPaginationFilter;
import com.service.onboarding.domain.requestdata.ServiceModelRequestData;
import com.service.onboarding.domain.resource.Greeting;
import com.service.onboarding.domain.resource.ServiceModel;
import com.service.onboarding.domain.resource.ServiceModelDetail;
import com.service.onboarding.domain.responsedata.PaginatedServiceResponseData;

/*
 * Sample service to demonstrate what the API would use to get things done
 */
@Service
public class OnboardingServiceImpl implements OnboardingService {

    private final AtomicLong counter = new AtomicLong();

    private static final String TEMPLATE = "Hello, %s!";

    private static final Logger LOG = LoggerFactory.getLogger(OnboardingServiceImpl.class);

    @Autowired
    private ServiceModelRepository serviceModelRepository;


    public OnboardingServiceImpl() {
    }

    @Override
    public Greeting getPersonalGreeting(final String name) {
        return new Greeting(counter.incrementAndGet(),
                String.format(TEMPLATE, name));
    }
 @Override
    public ServiceModelDetail createServiceModel(final ServiceModelRequestData serviceModelRequestData) throws MalformedContentException, ServiceModelNameAlreadyExistsException, ServiceUnavailableException {
        return serviceModelRepository.create(serviceModelRequestData);
    }

您的测试应如下所示:

package de.scrum_master.Whosebug

import org.springframework.http.ResponseEntity
import org.springframework.web.multipart.MultipartFile
import spock.lang.Specification
import spock.lang.Unroll

import static org.springframework.http.HttpStatus.*

class OnboardingControllerTest extends Specification {
  OnboardingService service = new OnboardingService()
  OnboardingController controller
  ResponseEntity response

  @Unroll
  def "HTTP response #statusCode when creating service model"() {
    given:
    if (exception) {
      service = Stub() {
        createServiceModel(_) >> { throw exception }
      }
    }
    controller = new OnboardingController(onboardingService: service)

    when:
    response = controller.createServiceModel("test", Mock(MultipartFile))

    then:
    response.statusCode == statusCode

    where:
    exception                                    | statusCode
    null                                         | OK
    new MalformedContentException()              | BAD_REQUEST
    new ServiceModelNameAlreadyExistsException() | CONFLICT
    new ServiceUnavailableException()            | SERVICE_UNAVAILABLE
  }
}

控制台日志(我在复制您的用例时切换到 Java 日志记录)将是:

Mär 10, 2018 12:51:50 PM de.scrum_master.Whosebug.OnboardingController createServiceModel

INFORMATION: Malformed Content: de.scrum_master.Whosebug.MalformedContentException
Mär 10, 2018 12:51:50 PM de.scrum_master.Whosebug.OnboardingController createServiceModel
INFORMATION: Service Model Name Already Exists: de.scrum_master.Whosebug.ServiceModelNameAlreadyExistsException
Mär 10, 2018 12:51:50 PM de.scrum_master.Whosebug.OnboardingController createServiceModel
INFORMATION: Service Unavailable currently: de.scrum_master.Whosebug.ServiceUnavailableException

在我的 IDE (IntelliJ IDEA) 中,测试结果如下所示:


P.S.:

我知道你是 SO 的新手。所以这是你的免费拍摄。下次请提供一个 MCVE,即一个最小的、可编译和可执行的示例,而不仅仅是没有包名、导入和测试的代码片段 class。您需要 Spock 方面的帮助,因此请编写一份 Spock 规范(测试 class),将其展示给我们并解释您的问题。这样我们就不需要从头开始重新创建您的情况,而可以专注于修复您的测试和解决您的问题。

幸好我只是觉得有点无聊,等客人来,不然我也不会那样做。说"I have nothing, please do everything for me"只能说明你很懒。如果您需要帮助,请让其他人更容易提供帮助,并对他们的时间预算表示尊重。他们是免费的!