最佳实践 - 在单元测试中设置不带setter的字段

时间:2015-02-27 06:29:48

标签: java unit-testing reflection mockito getter-setter

假设您有以下要测试的课程:

public class SomeService {
  public String someMethod(SomeEntity someEntity) {
    return someEntity.getSomeProperty();
  }
}

SomeEntity看起来像这样:

public class SomeEntity {
  private String someProperty;

  public getSomeProperty() {
    return this.someProperty;
  }
}

您想要做的断言如下:

String result = someService.someMethod(someEntity);

assertThat(result).isEqualTo("someValue");

如何使此测试工作?

1)为某些属性添加一个setter'在SomeEntity类中。我不认为这是一个很好的解决方案,因为您不会更改生产代码以使测试有效。

2)使用ReflectionUtils设置此字段的值。测试看起来像这样:

 public class TestClass {
      private SomeService someService;

        @Test
          public void testSomeProperty() {
            SomeEntity someEntity = new SomeEntity();
            ReflectionTestUtils.setField(someEntity, "someProperty", "someValue");

            String result = someService.someMethod(someEntity);

            assertThat(result).isEqualTo("someValue");
          }
}

3)在测试类中创建一个扩展SomeEntity类的内部类,并为该字段添加setter。但是,要实现此功能,您还需要更改SomeEntity类,因为该字段应该变为“受保护”字样。而不是私人'。测试类可能如下所示:

public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
   SomeEntityWithSetters someEntity = new SomeEntityTestWithSetters();
    someEntity.setSomeProperty("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }


  public class SomeEntityWithSetters extends SomeEntity {
   public setSomeProperty(String someProperty) {
     this.someProperty = someProperty;
   } 
  }
}

4)你使用Mockito来模拟SomeEntity。如果您只需要在类中仅模拟一个属性,那么似乎很好,但如果您需要像10个属性那样进行模拟,那该怎么办呢。测试可能如下所示:

public class TestClass {
  private SomeService someService;

  @Test
  public void testSomeProperty() {
    SomeEntity someEntity = mock(SomeEntity.class);
    when(someEntity.getSomeProperty()).thenReturn("someValue");

    String result = someService.someMethod(someEntity);

    assertThat(result).isEqualTo("someValue");
  }
}

5 个答案:

答案 0 :(得分:7)

您可以使用反射设置值。它不需要对生产代码进行任何更改。

ReflectionTestUtils.setField(YourClass.class,“fieldName”,fieldValue);

答案 1 :(得分:2)

您可以添加具有默认(包私有)范围的setter。

答案 2 :(得分:0)

使用SomeService.someMethod()的junit测试

替代1.不应该使用它,因为不需要更改实体来编写junit。

替代2.可以使用。

替代3.同样一个3,不需要为junit延伸。如何无法延长班级。

替代4.是的,一个不错的选择。因为同样的原因,使用了mockito。

答案 3 :(得分:0)

特定于SomeService的行为/合同是可测试的?根据您的骨架代码,确实没有。它会在坏输入上抛出一个NPE,或者返回一个可能为null但可能不为null的String,具体取决于Hibernate魔法。不确定你实际可以测试

答案 4 :(得分:0)

我之前经历过很多次这样的困境,一个快速的解决方案是让你想要模拟包受保护的字段,或者提供受保护的setter。当然两者都会改变生产代码。

或者,您可以考虑依赖注入框架,例如Dagger。以下是他们给出的一个例子:

@Module
class DripCoffeeModule {
  @Provides Heater provideHeater(Executor executor) {
    return new CpuHeater(executor);
  }
}
  

此JUnit测试使用Mockito中的模拟对象覆盖DripCoffeeModule对Heater的绑定。模拟器被注入到CoffeeMaker中并进入测试阶段。

public class CoffeeMakerTest {
  @Inject CoffeeMaker coffeeMaker;
  @Inject Heater heater;

  @Before public void setUp() {
    ObjectGraph.create(new TestModule()).inject(this);
  }

  @Module(
      includes = DripCoffeeModule.class,
      injects = CoffeeMakerTest.class,
      overrides = true
  )
  static class TestModule {
    @Provides @Singleton Heater provideHeater() {
      return Mockito.mock(Heater.class);
    }
  }

  @Test public void testHeaterIsTurnedOnAndThenOff() {
    Mockito.when(heater.isHot()).thenReturn(true);
    coffeeMaker.brew();
    Mockito.verify(heater, Mockito.times(1)).on();
    Mockito.verify(heater, Mockito.times(1)).off();
  }
}