【问题标题】:Problems with testing create method in Spock在 Spock 中测试创建方法的问题
【发布时间】:2020-12-04 14:03:19
【问题描述】:

大家好,我是 spock 的新手,我在为 save 方法和 setDefaultFormFieldConfig() 编写测试时遇到问题。你可以帮帮我吗?我不知道我的测试出了什么问题。

public ForConfig create(ForConfig forConfig, Long id) {
    setNormal(forConfig, "title", "offer.label.title", FieldType.IN);
    setNormal(forConfig, "shortDescription", "offer.label.shortDescription", FieldType.TEXTAREA);
    forConfig.setCategory(categService.findById(id));
    return forConRep.save(formConfig);
}



 private void setNormal(FormConfig forConfig, String n, String s, FieldType fieldType) {
        if (formConfig.getFc().stream()
                .noneMatch(fieldConfig -> fieldConfig.getName().equals(name))) {
            forConfig.getFieldsConfig()
                    .add(new FieldConfig(n, s, null, "M",
                            fieldType, false, false, true, null));
        }

测试保存方法:

def 'test create forConfig without normal fields'() {
    given:
    def forConfig = Mock(FormConfig)
    forConfig.getFieldsConfig() >> new ArrayList<Config>()
    def forConfRep = Mock(ForConRep);
    def categService = Mock(CategService);
    def impl = new ForConfServiceImpl(forConfRep, categService)
    forConfRep.save(_) >> new FormConfig()
    categoryService.findById(_) >> new Categ()

    when:
    impl.create(forConfig, 1)

    then:
    1 * forConfig.getFieldsConfig().add(_)
    2 * forConfig.setCategory(_)
}

在控制台中我收到以下错误:

Too few invocations for:

1 * formConfig.getFieldsConfig().add(_)   (0 invocations)

Unmatched invocations (ordered by similarity):

None

Too few invocations for:

2 * formConfig.setCategory(_)   (1 invocation)

Unmatched invocations (ordered by similarity):

None



    at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:98)
    at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:77)
    at pl.offer.service.impl.ForConfServiceImplSpec.test save formConfig without defaults fields(FormConfigServiceImplSpec.groovy:24)


Process finished with exit code -1

你能帮帮我吗?

【问题讨论】:

  • 欢迎来到 SO。感谢您提出有趣的问题。请注意了解MCVE 是什么以及为什么它对在这里获得合格的帮助非常有帮助:仅仅是因为它使您的问题可以重现。仅仅阅读具有许多依赖项(使用的类和对象)的相当复杂的代码,很难找到问题的根本原因。为了使应用程序和测试类编译,我必须创建许多帮助类,请参阅我的答案。

标签: spring-boot testing groovy spock


【解决方案1】:

你的测试有两个主要问题:

  1. 计数错误,您在then: 块中将它们颠倒了:

    • add(..) 方法应该被调用两次,因为setDefaultFormFieldConfig(..) 也被调用了两次。
    • setCategory(..) 方法只能调用一次。
  2. 如果您尝试验证add(..) 上的交互,这是一个List 方法,您必须为其注入一个模拟对象。为了简单起见并且为了避免不得不存根大量的List 方法,我建议只使用Spy 对象。但是,您不能将其编写为像1 * formConfig.getFieldsConfig().add(_) 这样的调用链,您必须直接验证目标模拟/间谍,例如2 * fieldConfigList.add(_).

这是MCVE

使应用程序和测试编译和运行的虚拟依赖类:

package de.scrum_master.stackoverflow.q65144983;

public class Category {
  private String name;

  public Category() { }

  public Category(String name) {
    this.name = name;
  }
}
package de.scrum_master.stackoverflow.q65144983;

public class CategoryService {
  public Category findById(Long categoryId) {
    return new Category("BY_ID_" + categoryId);
  }
}
package de.scrum_master.stackoverflow.q65144983;

public class FieldConfig {
  private String name;

  public FieldConfig(String name, String label, Object o, String match, FieldType type, boolean b, boolean b1, boolean b2, Object o1) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}
package de.scrum_master.stackoverflow.q65144983;

public enum FieldType {
  TEXTAREA, INPUT
}
package de.scrum_master.stackoverflow.q65144983;

import java.util.List;

public class FormConfig {
  private Category category;
  private List<FieldConfig> fieldsConfig;

  public FormConfig() {}

  public void setCategory(Category category) {
    this.category = category;
  }

  public List<FieldConfig> getFieldsConfig() {
    return fieldsConfig;
  }
}
package de.scrum_master.stackoverflow.q65144983;

public class FormConfigRepository {
  public FormConfig save(FormConfig formConfig) {
    return formConfig;
  }
}

被测接口+实现:

package de.scrum_master.stackoverflow.q65144983;

public interface FormConfigService {
  FormConfig save(FormConfig formConfig, Long categoryId);
}
package de.scrum_master.stackoverflow.q65144983;

public class FormConfigServiceImpl implements FormConfigService {
  private FormConfigRepository formConfigRepository;
  private CategoryService categoryService;

  public FormConfigServiceImpl(FormConfigRepository formConfigRepository, CategoryService categoryService) {
    this.formConfigRepository = formConfigRepository;
    this.categoryService = categoryService;
  }

  @Override
  public FormConfig save(FormConfig formConfig, Long categoryId) {
    setDefaultFormFieldConfig(formConfig, "title", "offer.label.title", FieldType.INPUT);
    setDefaultFormFieldConfig(formConfig, "shortDescription", "offer.label.shortDescription", FieldType.TEXTAREA);
    formConfig.setCategory(categoryService.findById(categoryId));
    return formConfigRepository.save(formConfig);
  }

  private void setDefaultFormFieldConfig(FormConfig formConfig, String name, String label, FieldType type) {
    if (
      formConfig.getFieldsConfig().stream()
        .noneMatch(fieldConfig -> fieldConfig.getName().equals(name))
    )
    {
      formConfig.getFieldsConfig()
        .add(new FieldConfig(name, label, null, "MATCH", type, false, false, true, null));
    }
  }
}

Spock 测试:

我希望你能接受我让 mock 更加类型安全和内联存根方法。否则,测试与您的测试几乎相同。我只修复了开头提到的两个问题。

package de.scrum_master.stackoverflow.q65144983

import spock.lang.Specification

class FormConfigServiceImplTest extends Specification {
  def 'test save formConfig without defaults fields'() {
    given:
    List fieldConfigList = Spy(new ArrayList<FieldConfig>())
    FormConfig formConfig = Mock() {
      getFieldsConfig() >> fieldConfigList
    }
    FormConfigRepository formConfigRepository = Mock() {
      save(_) >> new FormConfig()
    }
    CategoryService categoryService = Mock() {
      findById(_) >> new Category()
    }
    def formConfigService = new FormConfigServiceImpl(formConfigRepository, categoryService)

    when:
    formConfigService.save(formConfig, 1)

    then:
    2 * fieldConfigList.add(_)
    1 * formConfig.setCategory(_)
  }
}

P.S.:我向您展示了如何从技术上解决您使用 Spock 的问题。不过,我确实相信,这种测试实现内部的方法可能不是测试应用程序的正确方法。如果您进行一些内部重构,这些类型的测试往往很脆弱并且很容易中断。您还必须重构您的测试,这通常很好。但我建议您专注于测试应用程序的公共 API,而不是痴迷于内部工作,除非它们绝对重要。那么你当然可以而且也许应该测试它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    • 2014-06-21
    • 2014-09-24
    • 2020-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多