英文:
Junit tests fail due shared instances (i think)
问题
我认为我的问题与这个问题有关:
https://stackoverflow.com/questions/26561511/tests-pass-when-run-individually-but-not-when-the-whole-test-class-run
我的所有测试在单独运行时都通过,但当我运行测试类时,其中两个测试失败。调试显示,assertEquals 期望的状态与代码中给出的状态不同,主要是来自不同测试的状态。解决方案应该是使用 @Before 或 @After 进行清理,但是因为实例是由 Spring 的 Autowired 自动注入的,所以我在使用时遇到了问题。
主要问题:我的思路对吗,如何“重置”Autowired的实例?
@SpringBootTest
class StatementProcessorServiceTest {
@Autowired
StatementProcessorService statementProcessorService;
private StatementProcessorInputModel setup(long id, boolean endbalanceCorrect) {
StatementProcessorInputModel statementProcessorInputModel = new StatementProcessorInputModel();
statementProcessorInputModel.startBalance = 1.00;
statementProcessorInputModel.id= id;
statementProcessorInputModel.startBalance = 1.00;
statementProcessorInputModel.mutation = 1.00;
statementProcessorInputModel.endBalance = endbalanceCorrect ? 2.00 : 1.00;
return statementProcessorInputModel;
}
@Test
void verify_with_all_good_settings()
{
StatementProcessorResponseModel sut;
sut = statementProcessorService.validate(setup(1, true));
Assert.assertEquals(sut.result, "SUCCESSFUL");
}
@Test
void verify_with_good_settings_but_duplicated_reference()
{
StatementProcessorResponseModel sutFirst = statementProcessorService.validate(setup(2, true));
Assert.assertEquals(sutFirst.result, "SUCCESSFUL");
StatementProcessorResponseModel sutSecond;
sutSecond = statementProcessorService.validate(setup(2, true));
Assert.assertEquals(sutSecond.result, "DUPLICATE_REFERENCE");
}
@Test
void verify_with_wrong_endBalance_and_good_reference()
{
StatementProcessorResponseModel sut = statementProcessorService.validate(setup(3, false));
Assert.assertEquals(sut.result, "INCORRECT_END_BALANCE");
}
@Test
void verify_with_all_wrong_settings()
{
StatementProcessorResponseModel sutFirst = statementProcessorService.validate(setup(4, true));
Assert.assertEquals(sutFirst.result, "SUCCESSFUL");
StatementProcessorResponseModel sutSecond = statementProcessorService.validate(setup(4, false));
Assert.assertEquals(sutSecond.result, "DUPLICATE_REFERENCE_INCORRECT_END_BALANCE");
}
}
编辑 1:添加了 Service 代码
@Component
public class StatementProcessorService {
private StatementProcessorResponseModel responseModel = new StatementProcessorResponseModel();
private static ArrayList<Long> usedReferences = new ArrayList<>();
public StatementProcessorResponseModel validate(StatementProcessorInputModel inputModel){
if(!validateUniqueReference(inputModel.transactionReference))
{
responseModel.errorRecords.add("account_number_of_ inCorrectEndBalance _record");
responseModel.result = "DUPLICATE_REFERENCE";
}
if(!validateMutation(inputModel))
{
responseModel.errorRecords.add("account_number_of_ inCorrectEndBalance _record");
if (!responseModel.result.isBlank()){
responseModel.result = responseModel.result + "_INCORRECT_END_BALANCE";
}
else{
responseModel.result = "INCORRECT_END_BALANCE";
}
}
if (responseModel.result.isBlank()){
responseModel.result = "SUCCESSFUL";
}
return responseModel;
}
英文:
I think my problem is related to this issue:
https://stackoverflow.com/questions/26561511/tests-pass-when-run-individually-but-not-when-the-whole-test-class-run
My tests all pass individually but when i run the test class, two of them fail. Debugging shows that that assertEquals is expecting different states than given in the code, mainly the states from different tests. The solution should be using a @Before or @After and do a cleanup, but i am having troubles using that becouse the instance is Autowired from Spring.
Main question: Is my thinking right and how do i 'reset' the autowired instance?
@SpringBootTest
class StatementProcessorServiceTest {
@Autowired
StatementProcessorService statementProcessorService;
private StatementProcessorInputModel setup(long id, boolean endbalanceCorrect) {
StatementProcessorInputModel statementProcessorInputModel = new StatementProcessorInputModel();
statementProcessorInputModel.startBalance = 1.00;
statementProcessorInputModel.id= id;
statementProcessorInputModel.startBalance = 1.00;
statementProcessorInputModel.mutation = 1.00;
statementProcessorInputModel.endBalance = endbalanceCorrect ? 2.00 : 1.00;
return statementProcessorInputModel;
}
@Test
void verify_with_all_good_settings()
{
StatementProcessorResponseModel sut;
sut = statementProcessorService.validate(setup(1, true));
Assert.assertEquals(sut.result, "SUCCESSFUL");
}
@Test
void verify_with_good_settings_but_duplicated_reference()
{
StatementProcessorResponseModel sutFirst = statementProcessorService.validate(setup(2, true));
Assert.assertEquals(sutFirst.result, "SUCCESSFUL");
StatementProcessorResponseModel sutSecond;
sutSecond = statementProcessorService.validate(setup(2, true));
Assert.assertEquals(sutSecond.result, "DUPLICATE_REFERENCE");
}
@Test
void verify_with_wrong_endBalance_and_good_reference()
{
StatementProcessorResponseModel sut = statementProcessorService.validate(setup(3, false));
Assert.assertEquals(sut.result, "INCORRECT_END_BALANCE");
}
@Test
void verify_with_all_wrong_settings()
{
StatementProcessorResponseModel sutFirst = statementProcessorService.validate(setup(4, true));
Assert.assertEquals(sutFirst.result, "SUCCESSFUL");
StatementProcessorResponseModel sutSecond = statementProcessorService.validate(setup(4, false));
Assert.assertEquals(sutSecond.result, "DUPLICATE_REFERENCE_INCORRECT_END_BALANCE");
}
}
Edit 1: Added Service code
@Component
public class StatementProcessorService {
private StatementProcessorResponseModel responseModel = new StatementProcessorResponseModel();
private static ArrayList<Long> usedReferences = new ArrayList<>();
public StatementProcessorResponseModel validate(StatementProcessorInputModel inputModel){
if(!validateUniqueReference(inputModel.transactionReference))
{
responseModel.errorRecords.add("account_number_of_ inCorrectEndBalance _record");
responseModel.result = "DUPLICATE_REFERENCE";
}
if(!validateMutation(inputModel))
{
responseModel.errorRecords.add("account_number_of_ inCorrectEndBalance _record");
if (!responseModel.result.isBlank()){
responseModel.result = responseModel.result + "_INCORRECT_END_BALANCE";
}
else{
responseModel.result = "INCORRECT_END_BALANCE";
}
}
if (responseModel.result.isBlank()){
responseModel.result = "SUCCESSFUL";
}
return responseModel;
}
</details>
# 答案1
**得分**: 2
如果您使用`@Transactional`标记修改数据库的测试用例/类,测试运行程序将在测试后回滚事务。
您可以参考[关于测试事务的参考文档][1]。
<details>
<summary>英文:</summary>
If you mark your test cases/classes which modify the database with `@Transactional` then the test runner will rollback the transaction after the test.
You can refer to the [reference documentation regarding test transactions][1].
[1]: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html#testcontext-tx
</details>
# 答案2
**得分**: 0
看起来你并没有使用数据库来存储状态,所以在不了解你是如何存储这个状态的情况下,很难在不添加完整答案的情况下进行补充。不过不管怎样,`StatementProcessorService` 必定以某种方式存储了状态,因此我将尝试说明如何在测试之间重置这个状态,或许你可以根据自己的情况进行调整。
从一个基本的“存储在内存中”的示例开始:
```java
class StatementProcessorService {
// 假设你现在暂时将状态存储在内存中
// 这里我们创建了一个共享的映射,用来存储数据
private Map<String, String> state = new HashMap<>();
}
最简单的方法是在 StatementProcessorService
外部暴露一个方法来重置状态。
class StatementProcessorService {
// ...
/** 重置状态 */
@VisibleForTesting
public void reset() { state.clear(); }
}
你还可以使用一个注入的状态持有者,在测试期间,它本身可以暴露一个重置方法。
class StateHolder {
// 访问器/修改器方法
}
class TestStateHolder extends StateHolder {
// ...
public void reset() { ... }
}
class StatementProcessorService {
@Autowired
private StateHolder state;
}
最后,你也可以使用 Mockito 来模拟 StateHolder
。
@SpringBootTest
class StatementProcessorServiceTest {
@MockBean StateHolder state;
}
和 Spring 的正常做法一样,有很多种方法可以实现你的目标。
英文:
It seems you're not using a database to store state, so it's difficult to add a complete answer without knowing how you are storing this state. Regardless StatementProcessorService
must be storing state somehow, so I'll try to illustrate how you can reset this state between tests, and perhaps you can adapt it to your situation.
Starting from a basic "it's stored in memory" example
class StatementProcessorService {
// assuming you're storing state in memory for now
// here we're just creating a shared map, which is where we presume you're putting data
private Map<String, String> state = new HashMap<>();
}
The easiest way of doing this is to expose a method to reset the state from outside the StatementProcessorService
.
class StatementProcessorService {
// ...
/** Reset state */
@VisibleForTesting
public void reset() { state.clear(); }
}
You could also use an injected state holder, which during tests can itself expose a reset method
class StateHolder {
// accessor/mutator methods
}
class TestStateHolder extends StateHolder {
// ...
public void reset() { ... }
}
class StatementProcessorService {
@Autowired
private StateHolder state;
}
And finally, you could just mock the StateHolder
with Mockito
@SpringBootTest
class StatementProcessorServiceTest {
@MockBean StateHolder state;
}
As normal with Spring, there are a lot of ways of achieving your goal.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论