【问题标题】:Unit testing with mongodb repositories使用 mongodb 存储库进行单元测试
【发布时间】:2014-02-26 15:13:38
【问题描述】:

我已经构建了使用 MongoDB 的应用程序,但在测试时遇到了问题。

只要我使用 JPA 和一些关系数据库,我就使用了一些测试层,将持久性切换到内存数据库(linke HSQLDB 或 MySQL)用于测试目的。通过这种方式,我能够限制 IO 操作并加快测试速度。 但是对于 MongoDB 和 Spring Data,使用基于扩展 MongoRepository 的接口的存储库非常方便。

我的问题是在使用存储库时如何处理单元测试和功能测试?例如,我有一个简单的类,它被注释为 mongo 文档:

public class Company {

@Id
private String id;
@NotEmpty
private String name;
private String description;
private String website;
private String logo;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}

public String getWebsite() {
    return website;
}

public void setWebsite(String website) {
    this.website = website;
}

public String getLogo() {
    return logo;
}

public void setLogo(String logo) {
    this.logo = logo;
}    
}

及相关仓库:

@Repository
public interface CompanyRepository extends MongoRepository<Company, Serializable> {
}

在对应的控制器中使用:

@RestController
public class CompanyController {

private static final Logger log = LoggerFactory.getLogger(CompanyController.class);
@Autowired
private CompanyRepository repository;

@RequestMapping(value = "/company", method = RequestMethod.POST)
public void create(@Valid @RequestBody(required = true) Company company) {
    repository.save(company);
}
}

最后我做了两个测试(但它可能同时涵盖两个任务),涵盖控制器 api 和数据格式(我在其中模拟存储库以防止 IO 操作,并且效果很好),第二个我想确保传递的对象已成功持久化。当它出现时,它并不那么简单。首先(如果我错了,请纠正我)没有内存中的 mongo 实现。其次,由于我想是继承,我无法通过模拟执行 save 方法进行验证。

@ContextConfiguration(classes = Application.class)
@WebAppConfiguration
public class CompanyControllerNGTest extends AbstractTestNGSpringContextTests {

@Mock
private CompanyRepository repositoryMock;
@Autowired
@InjectMocks
private CompanyController controller;
private MockMvc mockMvc;

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

@DataProvider
public static Object[][] companyJsonProvider() {
    return new Object[][]{
        {Json.createObjectBuilder()
            .add("name", "JakasFirma")
            .add("description", "jakas firma opis")
            .add("website", "www.jakasfirma.com")
            .add("logo", "jakies logo")
            .build(), status().isOk()},
        {Json.createObjectBuilder()
            .add("name", "JakasFirma")
            .build(), status().isOk()},
        {Json.createObjectBuilder()
            .add("description", "jakas firma opis")
            .add("website", "www.jakasfirma.com")
            .add("logo", "jakies logo")
            .build(), status().isBadRequest()},
        {Json.createObjectBuilder()
            .build(), status().isBadRequest()},
    };
}

@Test(dataProvider = "companyJsonProvider", enabled = false)
public void apiTest(JsonObject companyJson, ResultMatcher expectedStatus) throws Exception {
    //given
    mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    String content = companyJson.toString();

    //when
    mockMvc.perform(post("/company").contentType(MediaType.APPLICATION_JSON).content(content)).
            //then
            andExpect(expectedStatus);

}

@Test
public void shouldCreate() throws Exception {
    //given

    //when
    controller.create(mock(Company.class));

    //then
    //verify(repositoryMock, times(1)).save(any(Iterable.class));
}
}

我考虑在控制器和存储库之间引入一个 dao 层,可以模拟和验证机器人,它增加了更多复杂性和强制封装存储库使用的每个方法。它也不能解决问题,而是将其部分移至较低级别。 有什么方法或做法可以帮助解决这类问题吗?或者也许我应该对 mongo 使用不同的方法?

【问题讨论】:

    标签: java spring mongodb unit-testing spring-data-mongodb


    【解决方案1】:

    对于单元测试,我会存根或编写 CompanyRepository 的实现并将其注入您的控制器(您可能需要为 CompanyRepository 添加一个 Setter 方法)。

    对于功能或集成测试,我会考虑使用以下方法

    @ContextConfiguration("file:my-context-file.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    

    我希望您在上下文文件中配置仅运行测试所需的 bean。

    【讨论】:

    • 重点是如何验证create方法是否导致持久化新实例。将关系数据库与 ORM 一起使用时,我只是将数据库层切换到内存并在测试数据库实例上做出断言。在 mongo 的情况下,我无法做到这一点,所以我考虑了模拟(就像你建议的那样)并测试 save 方法的执行。这样它也不起作用,因为 Mockito 无法处理继承自 MongoRepository (定义保存方法)的 CompanyRepository。当然 CompanyController 的 create 方法非常简单,但同样的问题存在于更复杂的操作中。
    • 我已经想通了。问题出在 varify 方法上: verify(repositoryMock, ti​​mes(1)).save(any(Iterable.class));应该有 Company.class 而不是 Iterable.class - 我没有注意到这一点,并认为 Mockito 无法验证继承的方法......现在可以完美运行。
    • 编写基于哈希图的 CompanyRepository 是我所做的,而且效果很好。由于所有存储库都有类似的接口,因此很容易为所有存储库编写基于抽象哈希图的实现。然后特定的存储库子类化抽象实现。
    【解决方案2】:

    我也遇到过同样的问题。我建议您使用MongoRepository 访问 MongoDB,原因如下:

    • 它用于简单的查询,例如 findByxxx 或 findOneByxxxAndyyy。如果您想要更复杂的查询,即使 @Query 也无法满足您的需求。
    • 很容易出现语法错误,因为所有查询条件都是由接口定义的。
    • 无法定义动态查询,例如根据上下文、字段、聚合等进行搜索...

    相反,我建议您使用 MongoOperations。它接受具有简单/复杂条件的动态查询、编写查询的非常简单的语法等...对于模拟,使用 Fongo 框架,因此它是 100% 在内存数据库中,因此您可以对断言执行所有 CRUD 操作...

    更多信息:http://docs.spring.io/spring-data/data-mongo/docs/current/api/org/springframework/data/mongodb/core/MongoOperations.html

    如何模拟 https://github.com/fakemongo/fongo#usage-details

    更新: 如果您仍需要模拟 MongoDB 存储库,请使用此 xml 定义:

    mongo-config.xml

    <?xml  version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:lang="http://www.springframework.org/schema/lang"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:util="http://www.springframework.org/schema/util"
        xmlns:mongo="http://www.springframework.org/schema/data/mongo"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/jee   http://www.springframework.org/schema/jee/spring-jee.xsd
            http://www.springframework.org/schema/lang  http://www.springframework.org/schema/lang/spring-lang.xsd
            http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
            http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">
    
        <bean name="fongo" class="com.github.fakemongo.Fongo">
            <constructor-arg value="InMemoryMongo" />
        </bean>
        <bean id="mongo" factory-bean="fongo" factory-method="getMongo" />
    
        <mongo:db-factory id="mongoDbFactory" mongo-ref="mongo" />
    
        <!-- localhost settings for mongo -->
        <!--<mongo:db-factory id="mongoDbFactory" /> -->
    
        <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
            <constructor-arg ref="mongoDbFactory" />
        </bean>
    
        <!-- Base package to scan the mongo repositories -->
        <!-- Set your CompanyRepository package -->
        <mongo:repositories base-package="package.to.repositories"  />
    
    </beans>
    

    用这种方式定义你的测试类:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"/mongo-config.xml"})
    public class CompanyTest {
    
        @Autowired
        private MongoOperations mongoOperations;
    
        @Resource
        private CompanyRepository companyRepository;
    
        @Test
        public void foo() {
            // Define test logic
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-16
      • 1970-01-01
      • 1970-01-01
      • 2016-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多