database mock store for unit testing database data
This is a library for serialization and deserialization pojos to different formats for saving into stable removable store. This data can be used in test cases or other jobs.
If we have a relational database with a bunch relations between tables
we can hardly use unit tests without mocking database by h2 or other embedded solutions.
This library maintains tools for doing this.
Basic idea is to find base javax.persistence.*
annotations(Entity,Table,Column,Id,ManyToOne,OneToOne
and etc) to build entity graph from pojos.
After that , based on that meta information , we can construct flat view of that pojo. It is similar as a csv file uploaded from databases.
Major goal is a possibility to get this entity from csv files exported from database.\
Thus we can construct a graph depending objects by @ManyToOne and @OneToOne annotations(@OneToMany and @ManyToMany are a partial cases for previouses)
For example we have a same structure:
@Table(schema=shop,name=order)
class Order{
@ManyToOne Customer customer
@ManyToOne Manager manager
@OneToMany List<OrderBasket> baskets
}
@Table(schema=shop,name=customer)
class Customer{
@OneToOne Address address
}
@Table(schema=shop,name=item)
class Item{
@OneToOne Code code
@OneToMany List<OrderBasket> baskets
}
@Table(schema=shop,name=basket)
class OrderBasket{
@ManyToOne Order order
@ManyToOne Item item
}
we have a graph :
| -> Customer -> Address
|-> Order ->| -> Manager
OrderBasket ->|
|-> Item -> Code
Using this lib we can save OrderBasket to file or directory and get 7 files with depending entities corresponded to tables from database:
shop.basket
fileshop.order
fileshop.customer
fileshop.address
fileshop.item
fileshop.code
fileshop.manager
fileFiles will contain columns and values separating ‘;’ and wrapped quotes.
"id";"comment";"amount";"order_time";"customer_id";"payment_id"
"1";"comment";"10";"2019-01-01T01:01";"1";""
you have 2 options for starting work with this library:
public class QueryableStoreTest extends AbstractJpaFileMock {
public QueryableStoreTest() {
super("ru.besok.db.mock.data.common");
}
@Test
public void test(){
List<Item> items = itemRepoFromDb.findAll();
toFile(items, "item/case"); // save objects to file or directory
QueryableStore store = store("item","case"); // get store for running objects
List<Item> itemsFromFile = store.all(Item.class);
}
}
...
List<Item> items = itemRepoFromDb.findAll();
MockFileInvoker invoker = new MockFileInvoker(pkgForScan);
invoker.toFile(items,ResourceUtils.resolveIn("item/case")); // save objects to file or directory
QueryableStore store = invoker.fromFile("item","case"); // get store for running objects
List<Item> itemsFromFile = store.all(Item.class);
...
if source or destination is a directory each entity is in a separated file named schema.table\ Otherwise each entity separated by a header _@@@_schema.table
Files have a csv similar syntax:
QueryableStore let you process followings commands:
all(Class<V> vClass)
find all entities from this dir or file by classany(Class<V> vClass)
find random entity from this dir or file by classanyByField | allByField(Class<V> vClass, fieldName, fieldValue)
find all entities from this
dir or file by class by fieldValue(field can be composite see notes)byId(Class<V> vClass, Object id)
find entity by idCommon case is we can do some programs logic without spring context by mocking components especially Repositories.
public class CommonCaseExampleTest extends AbstractJpaFileMock {
public CommonCaseExampleTest() {
super("your.package.with.pojos");
}
AppComponent component;
@Before
public void setUp() throws Exception {
QueryableStore store = store("test_dir");
OrderRepo orderRepo = Mockito.mock(OrderRepo.class);
List<Order> orders = store.all(Order.class); // we can get all orders from file instead of database
Mockito.when(orderRepo.findAll()).thenReturn(orders);
component = new AppComponent(orderRepo);
}
@Test
public void test() {
int result = component.doSomeLogic();
Assert.assertEquals(42,result);
}
public class AppComponent {
private OrderRepo orderRepo;
AppComponent(OrderRepo orderRepo) {
this.orderRepo = orderRepo;
}
public int doSomeLogic(){
List<Order> orders = orderRepo.findAll();
int res = 42; // some hard logic
return res;
}
}
}
If you have structure like that:
class Customer{
@OneToMany List<Order> orders;
}
class Order{
@ManyToOne Customer customer;
@OneToMany List<OrderBasket> baskets;
}
class Item{
@OneToMany List<OrderBasket> baskets
}
class OrderBasket{
@ManyToOne Order order;
@ManyToOne Item item;
}
and you want to drop to file all OrderBaskets from Customer you can do something like that:
List<OrderBasket> baskets =
customer.getOrders().stream()
.map(Order::getBaskets)
.flatMap(Collection::stream)
.collect(Collectors.toList());
toFile(baskets,"path_for_saving");
if you have some fields with uncommon format ,for example for date you can get StringMapException. For solving this you can do your own String mapper:
"id";"time";"name"
"1";"2019-01-01 00:00";"new_year_party"
For solving this you can do your own String mapper by implementing AbstractStringMapper:
public class UserStringMapper extends AbstractStringMapper {
@Override
public StringFunction<LocalDateTime> localDateTime() {
return s -> LocalDateTime.parse(s, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
}
}
And mix it to a invoker:
public class CommonCaseExampleTest extends AbstractJpaFileMock {
@Before
public void setUp() throws Exception {
withMapper(new UserStringMapper());
}
}
...
invoker.withMapper(new UserStringMapper());
if you have several databases with different packages for scan you have to have several invokers or switch one invoker all time:
public class CommonCaseExampleTest extends AbstractJpaFileMock {
public CommonCaseExampleTest() {
super("pkg.for.first.db");
}
SpringComponent component;
@Before
public void setUp() throws Exception {
QueryableStore storeForFirstDb = store("test_dir");
MockFileInvoker invoker = invoker("pkg.for.second.db");
QueryableStore storeForSecondDb = invoker.fromFile(ResourceUtils.resolveIn("other_dir"));
}
}
public class CommonCaseExampleTest extends AbstractJpaFileMock {
public CommonCaseExampleTest() {
super("pkg.for.first.db");
}
SpringComponent component;
@Before
public void setUp() throws Exception {
QueryableStore storeForFirstDb = store("test_dir");
withPackage("pkg.for.second.db");
QueryableStore storeForSecondDb = store("other_dir");
}
}
Source or destination is a directory the lib put each entity into separate file named schema.table \ from @Table annotation or Class Name transforming camelcase to snake case.\ Otherwise the lib put it into a file. The separator is _@@@_schema.table.
QueryableStore assumed to find entity by composite field.\
For example Order{customer:Customer{address:Address{street:String}}}
\
And you can do: store.anyByField(Order.class,"customer.address.street","mystreet5")
This lib does not cover all possible cases for java persistent annotation. It plans to do further.