Модульное тестирование с репозиториями mongodb

Я создал приложение, использующее MongoDB, и столкнулся с проблемой тестирования.

Пока я использовал JPA и некоторую реляционную базу данных, я использовал некоторый тестовый уровень, который переключал постоянство на базу данных в памяти (linke HSQLDB или MySQL) для целей тестирования. Таким образом я смог ограничить операции ввода-вывода и ускорить тесты. Однако с MongoDB и Spring Data очень удобно использовать репозитории на основе интерфейсов, расширяющих MongoRepository.

Мой вопрос: как бороться с модульным тестированием и функциональным тестированием при использовании репозиториев? Например, у меня есть простой класс, который помечен как документ монго:

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-интерфейс контроллера и формат данных (где я имитирую репозиторий, чтобы предотвратить операции ввода-вывода, и он работает хорошо), и второй, где я хочу убедиться, что переданный объект был успешно сохранен. Как оказалось не все так просто. Прежде всего (поправьте меня, если я ошибаюсь) нет реализации монго в памяти. Во-вторых, я не могу проверить с помощью mockito выполнение метода сохранения из-за наследования, я полагаю.

@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 между контроллером и репозиторием, который можно было бы смоделировать и проверить ботом, это добавляет сложности и силы для инкапсуляции каждого метода, используемого репозиторием. Также это не устраняет проблему, а частично перемещает ее на более низкий уровень. Есть ли какой-либо метод или практика, которые могли бы помочь справиться с такой проблемой? Или, может быть, мне следует использовать другой подход с монго?


person Wojciech Wąsik    schedule 03.02.2014    source источник


Ответы (2)


Для модульного тестирования я бы заглушил или написал реализацию CompanyRepository и внедрил бы ее в ваш контроллер (вам может потребоваться добавить метод Setter для CompanyRepository).

Для функционального или интеграционного тестирования я бы посмотрел на использование следующего

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

Я ожидаю, что в файле контекста вы настроите bean-компоненты, которые необходимы только для запуска вашего теста.

person ebell    schedule 03.02.2014
comment
Дело в том, как проверить, приводит ли метод create к сохранению нового экземпляра. При использовании реляционной базы данных с ORM я просто переключил слой базы данных на память и сделал утверждения в тестовом экземпляре базы данных. В случае с монго я не могу сделать это таким образом, поэтому я подумал о насмешке (как вы предложили) и проверил выполнение метода сохранения. Таким образом, это тоже не работает, потому что Mockito не может работать с CompanyRepository, которые наследуются от MongoRepository (который определяет метод сохранения). Конечно, метод создания CompanyController очень прост, но та же проблема существует и с гораздо более сложными действиями. - person Wojciech Wąsik; 03.02.2014
comment
Я понял это. Проблема была с методом вариации: verify(repositoryMock, times(1)).save(any(Iterable.class)); Вместо Iterable.class должен быть Company.class - я этого не заметил и подумал, что Mockito не может проверить унаследованный метод... Теперь работает отлично. - person Wojciech Wąsik; 03.02.2014
comment
Написание CompanyRepository на основе хэш-карты — это то, что я сделал, и это прекрасно работает. Поскольку все репозитории имеют схожий интерфейс, довольно легко написать абстрактную реализацию на основе хэш-карты для всех репозиториев. Затем конкретные репозитории подклассируют абстрактную реализацию. - person rwitzel; 21.03.2014

Я столкнулся с той же проблемой. Я рекомендую вам НЕ использовать 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
    }

}
person Valijon    schedule 15.08.2015