模拟 class 嵌套在 classes 中用于测试

Mock out class nested inside classes for testing

Alpha - parent beta 为 child

public class Alpha {
  Beta beta;

  public Alpha(int argument) {}

  void start() {
    beta = createBeta();
  }

  Beta createBeta() {
    return new Beta(this);
  }
}

Beta - child of alpha,有 charlie

public class Beta {
  Alpha alpha;
  Charlie charlie;

  public Beta(Alpha alpha) {
    this.alpha = alpha;
    this.charlie = createCharlie();
  }

  Charlie createCharlie() {
    return new Charlie().shuffle();
  }
}

Charlie - 有一个列表,通常在生产中被洗牌

public class Charlie {
  List<Integer> list = new ArrayList<Integer>();

  public Charlie() {
    for (int i = 0; i < 6; i++) {
      list.add(i);
    }
  }

  public Charlie shuffle() {
    Collections.shuffle(list);
    return this;
  }

  @Override
  public String toString() {
    return list.toString();
  }
}

AlphaTest [此测试需要帮助] - 想尝试不同的洗牌变体以查看 alpha/beta 的反应。

public class AlphaTest {

  Charlie special = new Charlie();

  @Test
  public void testSpecialCharlie() {
    Alpha alpha = Mockito.spy(new Alpha(0));
    Beta beta = Mockito.spy(alpha.createBeta());

    Mockito.when(alpha.createBeta()).thenReturn(beta);
    Mockito.when(beta.createCharlie()).thenReturn(special);

    alpha.start();

    // FAILURE: expected:<[0, 1, 2, 3, 4, 5]> but was:<[0, 4, 1, 5, 3, 2]>
    assertEquals(special.list, alpha.beta.charlie.list);
  }
}

目标是用查理的不同组合来测试 Alpha/Beta。不确定最好的方法是什么?这是复制的确切代码。也可以更改设置以方便测试。尝试了不同的变体,但没有任何效果。非常感谢对此的帮助。

我不确定,我试了很多方法(mocking out createCharlie() function,mocking out Charlie class,mocking out shuffle(),移动createCharlie() 到 parent Alpha class,没有真正正常工作,或者我可能遗漏了一些东西。非常感谢任何帮助。谢谢!

想知道为什么我不能这样做:

Charlie charlie = Mockito.mock(Charlie.class);
Mockito.when(charlie.shuffle()).thenReturn(special);

如果有人觉得有用,我会在下面留下我的初步答案。

首先,在构造函数的最后一行 Beta 中输入:

System.out.println("Charlie created: " + this.charlie.hashCode());

在运行测试之后你会看到charlie被创建了多次。

第一次在测试中调用时:

Beta beta = Mockito.spy(alpha.createBeta());

以后调用时:

Mockito.when(alpha.createBeta()).thenReturn(beta);

所以 Beta 保留对真实 Charlie 的引用,因为你在调用代码时模拟了 createCharlie()

Assert.assertEquals(special.list, alpha.beta.charlie.list);

真正的 Charlie 被称为,而不是模拟的。您实现 classes 的方式并不是很好,但如果我只是回答您应该在测试中更改的内容 - 您应该改为这样称呼:

Assert.assertEquals(special.list, alpha.beta.createCharlie().list);

在那种情况下,mocked Charlie 将被调用并且测试将通过。


上一个回答:

通常当你对某些东西进行单元测试时,你的重点是单个测试 class。您可以使用 @Spy/@InjectMocks。当你将测试Beta时,你应该模拟Charlie,当你将测试Alpha时,你将模拟Beta...

我不知道目的,但您的代码中的奇怪之处在于 createCharlie()randomize() returns Charlie 的实例。无论如何,你可以通过 getter 懒惰地创建 Charlie,像这样:

public class Beta {
    private Charlie charlie;

    public Beta() {
    }

    public Charlie getCharlie() {
        if (charlie == null) {
            charlie = new Charlie();
        }
        return charlie;
    }

    public void doSomethingWithCharlie() {
        getCharlie().randomize();
    }
}

你可以像这样在你的测试中注入 charlie:

public class BetaTest{
    @Mock(name="charlie") // variable name in Beta
    private Charlie charlieMock;

    @InjectMocks
    private Beta beta;

    @BeforeMethod
    public void before() {
        MockitoAnnotations.initMocks(this);
    }

    public void test1(){
        beta.doSomethingWithCharlie();
    }
}

另请参阅 @InjectMocks 文档。

您可以选择将包 protected/public 方法添加到 Alpha:

Beta createBeta() {
    return new Beta(); 
}

Beta getBeta() {
    if(beta==null){
        beta = new Beta();
    }

    return beta;
}

然后你可以注入 Alpha 真实的 Beta,returns 模拟 Charlie:

public class AlphaTest { @嘲笑 查理查理莫克;

@Spy
Beta beta;

@Spy
Alpha alpha;

public void beforeTest() {
    when(alpha.getBeta()).thenReturn(beta);
    when(beta.getCharlie()).thenReturn(charlie);
}

}

另见 this

感谢 Dario 的回答,我将 Beta class 更改为

public class Beta {
  Alpha alpha;
  Charlie _charlie;

  public Beta(Alpha alpha) {
    this.alpha = alpha;
    this._charlie = getCharlie();
    // PROBLEM: If I use this._charlie here, that will use the wrong charlie!
  }

  Charlie getCharlie() {
    if (_charlie == null) {
      _charlie = new Charlie().shuffle();
    }
    return _charlie;
  }
}

然后,我从不使用 alpha.beta._charlie 访问 charlie,而是始终使用 getCharlie(),这应该可以解决我的问题。谢谢达里奥,非常感谢!