英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论