在 Spock 中测试创建方法的问题

Problems with testing create method in Spock

Hey guys, I am new in spock and i have problems with writing tests for save method and setDefaultFormFieldConfig(). Could You help me? i don't know what is wrong with my test.

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));
        }

Test for save method:

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(_)
}

And in the console I get these errors:

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

Could You help me?

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

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

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

这是一个 MCVE:

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

package de.scrum_master.Whosebug.q65144983;

public class Category {
  private String name;

  public Category() { }

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

public class CategoryService {
  public Category findById(Long categoryId) {
    return new Category("BY_ID_" + categoryId);
  }
}
package de.scrum_master.Whosebug.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.Whosebug.q65144983;

public enum FieldType {
  TEXTAREA, INPUT
}
package de.scrum_master.Whosebug.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.Whosebug.q65144983;

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

正在测试的接口+实现:

package de.scrum_master.Whosebug.q65144983;

public interface FormConfigService {
  FormConfig save(FormConfig formConfig, Long categoryId);
}
package de.scrum_master.Whosebug.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));
    }
  }
}

斯波克测试:

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

package de.scrum_master.Whosebug.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 从技术上解决您的问题。不过我确实相信,这种测试实现内部结构的方法可能不是测试应用程序的正确方法。如果您进行一些内部重构,这些类型的测试往往很脆弱并且很容易破坏。您还必须重构您的测试,这通常没问题。但我建议您专注于测试应用程序的 public API,不要着迷于内部工作,除非它们绝对重要。那么你当然可以而且也许应该测试它们。