在Java中模拟一个不返回实例的单例对象。

huangapple go评论199阅读模式
英文:

Mocking a singleton that does not return the instance in Java

问题

因此,我有一个需要模拟的类,因为我不希望测试实际执行此连接,我想为privateMethodCall()提供虚假结果:

  1. public final Connection {
  2. private Connection() {
  3. //做一些事情
  4. }
  5. public static void get(/*一些参数*/) {
  6. Connection conn = new Connection();
  7. conn.privateMethodCall();
  8. }
  9. }

问题是,我看到的大多数单例模式都是在公共静态方法中返回实例,但这个不是。

在我正在测试的类中,Connection类只是在我试图测试的方法(prepareForThing())中的一个私有方法中调用。

  1. import Connection;
  2. public class ManagerClass {
  3. ManagerClass(/* 一些参数 */){
  4. //做一些事情
  5. }
  6. void prepareForThing(/* 参数 */) {
  7. // 做一些事情
  8. doThing();
  9. }
  10. private void doThing(/* 更多参数 */) {
  11. // 做一些事情
  12. Connection.get();
  13. // 继续做其他事情
  14. }
  15. }

即使我创建另一个构造函数并像建议的那样传递模拟类https://stackoverflow.com/questions/39074628/using-singleton-with-interfaces-java,由于实例仅在静态方法中创建和使用,它也无法正常工作。

到目前为止,我的解决方法是向正在测试的类传递一个布尔值isTest,然后修改Connection类,以便它可以被扩展,编写一个ConnectionMock,如果isTest设置为true,则修改调用。但这会使ManagerClass依赖于一个测试类ConnectionMock,这不是一个好方法。

但我不知道是否可能有更好的解决方法,使用Mockito或其他方法。我希望模拟在其他测试中可用,而不仅仅为测试创建它。而且我不确定扩展此类是否是最佳方法,但我不能创建一个接口,因为Connection中的所有方法都是私有的或静态的。有任何想法吗?

英文:

So, I have a class I need to mock, since I don't want the tests to actually do this connection, I want to provide fake results for the privateMethodCall():

  1. public final Connection {
  2. private Connection() {
  3. //does some stuff
  4. }
  5. public static void get(/*some params*/) {
  6. Connection conn = new Connection();
  7. con.privateMethodCall();
  8. }
  9. }

The issue is most singletons I see mocked are returning the instance in the public static method, this one is not.

In the class I'm testing, the Connection class is just called in a private method from the method I'm trying to test (prepareForThing()).

  1. import Connection;
  2. public class ManagerClass {
  3. ManagerClass(/* some params*/){
  4. //Does some stuff
  5. }
  6. void prepareForThing(/* params */) {
  7. // Does stuff
  8. doThing();
  9. }
  10. private void doThing(/* more params */) {
  11. // Does some stuff
  12. Connection.get();
  13. // Continues doing stuff
  14. }
  15. }

Even if I create another constructor and pass the mock class like is suggested https://stackoverflow.com/questions/39074628/using-singleton-with-interfaces-java, since the instance is just created and used within the static method it wouldn't work.

My workaround so far is to pass a boolean isTest to the class I'm testing, and then modify the Connection class so it can be extended, write a ConnectionMock, and if the isTest is set to true, modify the call. But this makes the ManagerClass depend on a test class, ConnectionMock, which is not good

But I don't know if there might be a better workaround with mockito or something. I would like the mock to be available for other tests, rather than create it for the test only. And I'm not sure extending this class is the best approach, but I can't create an interface because all methods in Connection are private or static. Any ideas?

答案1

得分: 1

一个可能的解决方案是隐藏您的单例。单例本质上很难测试,因为您只有一个实例和一个实现。所有使用静态方法的客户端都与它们_紧密耦合_。

请注意,您的 Singleton 不是经典意义上的真正单例,因为存在多个实例。

将您的单例包装起来,并将包装器注入到您的客户端以隐藏它。

  1. public final ConnectionSingleton {
  2. private ConnectionSingleton() {
  3. //做一些事情
  4. }
  5. public static void get(/*一些参数*/) {
  6. ConnectionSingleton conn = new ConnectionSingleton();
  7. con.privateMethodCall();
  8. }
  9. }
  10. public interface Connection {
  11. void get();
  12. }
  13. public class ProductionConnection implements Connection {
  14. @Override
  15. public void get() {
  16. Connection.get(); // 生产实现委托给单例
  17. }
  18. }
  1. import Connection;
  2. public class ManagerClass {
  3. private final Connection connection; // 依赖于接口
  4. ManagerClass(Connection connection){
  5. this.connection = connection;
  6. }
  7. void prepareForThing(/* 参数 */) {
  8. // 做一些事情
  9. doThing();
  10. }
  11. private void doThing(/* 更多参数 */) {
  12. // 做一些事情
  13. connection.get(); // 调用接口的实例方法
  14. // 继续做其他事情
  15. }
  16. }

在您的生产代码中,实例化您的管理器为 new ManagerClass(new ProductionConnection())。在测试中,注入一个模拟对象或自定义的虚拟实现:

  1. class TestConnection implements Connection {
  2. @Override public void get() { /* 什么也不做 */ }
  3. }
  4. @Test
  5. void myTest() {
  6. ManagerClass manager = new ManagerClass(new TestConnection());
  7. manager.prepareForThing();
  8. }

由于这个接口只有一个方法,您也可以传递一个空语句 lambda 表达式:new ManagerClass(() -> {})

您可能会在相关但不完全相同的问题 https://stackoverflow.com/questions/74027324/why-are-my-mocked-methods-not-called-when-executing-a-unit-test 中找到解决底层问题的其他解决方案(即在您的方法中使用 new 的问题)。

英文:

One possible solution is to hide your singleton. Singletons are inherently hard to test, because you only have a single instance and a single implementation. All clients of static methods are tightly coupled to them.

Note that your Singleton isn't a real singleton in the classical sense, because multiple instance of it exist.

Wrap your singleton and inject the wrapper to your clients to hide it.

  1. public final ConnectionSingleton {
  2. private ConnectionSingleton() {
  3. //does some stuff
  4. }
  5. public static void get(/*some params*/) {
  6. ConnectionSingleton conn = new ConnectionSingleton();
  7. con.privateMethodCall();
  8. }
  9. }
  10. public interface Connection {
  11. void get();
  12. }
  13. public class ProductionConnection implements Connection {
  14. @Override
  15. public void get() {
  16. Connection.get(); // production implementation delegates to singleton
  17. }
  18. }
  1. import Connection;
  2. public class ManagerClass {
  3. private final Connection connection; // depend on the interface
  4. ManagerClass(Connection connection){
  5. this.connection = connection;
  6. }
  7. void prepareForThing(/* params */) {
  8. // Does stuff
  9. doThing();
  10. }
  11. private void doThing(/* more params */) {
  12. // Does some stuff
  13. connection.get(); // call the instance method of the interface
  14. // Continues doing stuff
  15. }
  16. }

In your production code instantiate your manager as new ManagerClass(new ProductionConnection()). In your test, inject a mock or a custom dummy implementation:

  1. class TestConnection implements Connection {
  2. @Override public void get() { /* do nothing */ }
  3. }
  4. @Test
  5. void myTest() {
  6. ManagerClass manager = new ManagerClass(new TestConnection());
  7. manager.prepareForThing();
  8. }

Since this interface only has a single method, you could pass an empty statement lambda too: new ManagerClass(() -> {}).

You might find other solutions for the underlying problem (which is using new in your methods) in the related, but not quite duplicated, question https://stackoverflow.com/questions/74027324/why-are-my-mocked-methods-not-called-when-executing-a-unit-test

huangapple
  • 本文由 发表于 2023年7月3日 16:39:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76603138.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定