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

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

Mocking a singleton that does not return the instance in Java

问题

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

public final Connection {
  private Connection() {
    //做一些事情  
  }

  public static void get(/*一些参数*/) {
    Connection conn = new Connection();
    conn.privateMethodCall();
  }
}        

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

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

import Connection;

public class ManagerClass {
  ManagerClass(/* 一些参数 */){
    //做一些事情
  }

  void prepareForThing(/* 参数 */) {
    // 做一些事情
    doThing();
  }

  private void doThing(/* 更多参数 */) {
    // 做一些事情
    Connection.get();
    // 继续做其他事情
  }
}

即使我创建另一个构造函数并像建议的那样传递模拟类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():

public final Connection {
  private Connection() {
    //does some stuff  
  }

  public static void get(/*some params*/) {
    Connection conn = new Connection();
    con.privateMethodCall();
  }
}        

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()).

import Connection;

public class ManagerClass {
  ManagerClass(/* some params*/){
    //Does some stuff
  }

  void prepareForThing(/* params */) {
    // Does stuff
    doThing();
  }

  private void doThing(/* more params */) {
    // Does some stuff
    Connection.get();
    // Continues doing stuff
  }
}

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 不是经典意义上的真正单例,因为存在多个实例。

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

public final ConnectionSingleton {
  private ConnectionSingleton() {
    //做一些事情  
  }

  public static void get(/*一些参数*/) {
    ConnectionSingleton conn = new ConnectionSingleton();
    con.privateMethodCall();
  }
}

public interface Connection {
  void get();
}

public class ProductionConnection implements Connection {
  @Override
  public void get() {
    Connection.get(); // 生产实现委托给单例
  }
}
import Connection;

public class ManagerClass {
  private final Connection connection; // 依赖于接口
  ManagerClass(Connection connection){
    this.connection = connection;
  }

  void prepareForThing(/* 参数 */) {
    // 做一些事情
    doThing();
  }

  private void doThing(/* 更多参数 */) {
    // 做一些事情
    connection.get(); // 调用接口的实例方法
    // 继续做其他事情
  }
}

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

class TestConnection implements Connection {
  @Override public void get() { /* 什么也不做 */ }
}

@Test
void myTest() {
  ManagerClass manager = new ManagerClass(new TestConnection());
  manager.prepareForThing();
}

由于这个接口只有一个方法,您也可以传递一个空语句 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.

public final ConnectionSingleton {
  private ConnectionSingleton() {
    //does some stuff  
  }

  public static void get(/*some params*/) {
    ConnectionSingleton conn = new ConnectionSingleton();
    con.privateMethodCall();
  }
}

public interface Connection {
  void get();
}

public class ProductionConnection implements Connection {
  @Override
  public void get() {
    Connection.get(); // production implementation delegates to singleton
  }
}
import Connection;

public class ManagerClass {
  private final Connection connection; // depend on the interface
  ManagerClass(Connection connection){
    this.connection = connection;
  }

  void prepareForThing(/* params */) {
    // Does stuff
    doThing();
  }

  private void doThing(/* more params */) {
    // Does some stuff
    connection.get(); // call the instance method of the interface
    // Continues doing stuff
  }
}

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

class TestConnection implements Connection {
  @Override public void get() { /* do nothing */ }
}

@Test
void myTest() {
  ManagerClass manager = new ManagerClass(new TestConnection());
  manager.prepareForThing();
}

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:

确定