Skip to content

LLB Testing Specification

Conner wrote a JooqMock class to supplement our testing infrastructure for LLB's backend API, in addition to JUnit. In this documentation, we'll first explain the purpose of JooqMock, followed by a rundown of its methods. Carefully annotated examples of how public JooqMock methods would be used in a test will also be provided. See the JavaDoc for additional details.

Purpose

The purpose of JooqMock is to serve as a mock for testing database interactions, specifically CRUD operations (create/read/update/delete).

Examples of calls to all CRUD queries in our implementation are:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SELECT statement (getAnnouncements in AnnouncementsProcessorImpl.java)
List<Announcements> announcements = 
    db.selectFrom(ANNOUNCEMENTS)
            .where(ANNOUNCEMENTS.CREATED.between(start, end))
            .and(ANNOUNCEMENTS.EVENT_ID.isNull())
            .orderBy(ANNOUNCEMENTS.CREATED.desc())
            .fetchInto(Announcements.class);

// CREATE statement (createRequest in RequestsProcessorImpl.java)
PfRequestsRecord newRecord = db.newRecord(PF_REQUESTS);
newRecord.setUserId(userData.getUserId());
newRecord.setStatus(RequestStatus.PENDING);
newRecord.store();

// UPDATE statement (approveRequest in RequestsProcessorImpl.java)
db.update(USERS)
    .set(USERS.PRIVILEGE_LEVEL, PrivilegeLevel.PF)
    .where(USERS.ID.eq(requestsRecord.getUserId()))
    .execute();

// DELETE statement (deleteAnnouncement in AnnouncementsProcessorImpl.java)
db.delete(ANNOUNCEMENTS).where(ANNOUNCEMENTS.ID.eq(announcementId)).execute();

void addReturn(String operation, Record record)

The addReturn method is used to add an individual record associated with a given operation. The record will be added to the queue of records that will be returned from the execute() method. In other words, it essentially adds the record that would be returned if we queried the database with the given operation.

1
2
3
4
5
6
// Example from testGetAnnouncements3 in AnnouncementsProcessorImplTest.java

// announcement1 is the record that will be returned next time a SELECT is invoked
myJooqMock.addReturn("SELECT", announcement1);
// internally invokes a SELECT
GetAnnouncementsResponse res = myAnnouncementsProcessorImpl.getAnnouncements(req);

void addReturn(String operation, List<? extends Record> records)

This overloaded addReturn method is used to add multiple records associated with an operation to a list of records to the queue of records returned by the execute() method. The hashmap that stores the records to return by execute() separates the output by operation, so all of the records given to this method would be the result of querying the database with the given "operation" multiple times.

1
2
3
4
5
6
7
8
// Example from testGetAnnouncements2 in AnnouncementsProcessorImplTest.java

// List of announcements to be used as mock data
List<AnnouncementsRecord> announcements = new ArrayList<>();
announcements.add(announcement1);
announcements.add(announcement2);
// internally invokes a SELECT
myJooqMock.addReturn("SELECT", announcements);

void addReturn(String operation, Supplier<Result<? extends Record>> recordFunction)

This is used if we want to add records with a Supplier that returns records. A Supplier is merely an interface in Java that helps support lambda functions, read more here. We've never needed to use this overloaded method before so there is no example, but it could be useful later.

void addReturn(Map<String, List<? extends Record>> records)

If we have a map from a CRUD operation string to the records associated with that operation, this method may come in handy by building our mock directly from that map.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Example (modified) from testChangePassword3 in ProtectedUserProcessorImplTest.java

// mock the user in the DB
UsersRecord myUsersRecord = new UsersRecord();
myUsersRecord.setId(0);
myUsersRecord.setPassHash(Passwords.createHash("currentpasswd"));

List<UsersRecord> recordList = new ArrayList<>();
recordList.add(myUsersRecord);

// map from multiple operations to records
Map<String, List<UsersRecord>> recs = new HashMap<>();
recs.put("SELECT", recordList);
recs.put("UPDATE", recordList);

myJooqMock.addReturn(recs);

void addEmptyReturn(String operation)

Something needs to be returned each time a query is made on our mock, even if it's nothing. This method is a convenient way to say return no results the next time an operation is used.

1
2
3
4
5
6
7
8
9
// Example from testGetRequestStatus1 in RequestsProcessorImplTest.java

// SELECT would return no statuses here
JWTData myUserData = new JWTData(1, PrivilegeLevel.GP);
myJooqMock.addEmptyReturn("SELECT");

List<RequestStatusData> statuses = myRequestsProcessorImpl.getRequestStatuses(myUserData);

assertTrue(statuses.isEmpty());

int timesCalled(String operation)

Gets the number of times a operation was invoked on a mock.

1
2
3
4
5
6
7
8
9
// Example modified from above

// SELECT would return no statuses here
JWTData myUserData = new JWTData(1, PrivilegeLevel.GP);
myJooqMock.addEmptyReturn("SELECT");

List<RequestStatusData> statuses = myRequestsProcessorImpl.getRequestStatuses(myUserData);

assertEquals(1, myJooqMock.timesCalled("SELECT"));

Map<String, List<String>> getSqlStrings()

Get the strings associated with each respective SQL query called. Not used yet, but could useful for debugging.

Map<String, List<Object[]>> getSqlBindings()

Get the objects associated with each respective SQL query called. This is more compatible for testing than just plain strings, so we use this instead. You'll want to use this to test that the bindings on UPDATE operations are correct, to be specific.

1
2
3
4
5
6
7
8
9
// Example from testApproveRequest2 in RequestsProcessorImplTest.java

// method that calls UPDATE to mutate our DB data
myRequestsProcessorImpl.approveRequest(0, myUserData);

assertEquals(myJooqMock.getSqlBindings().get("UPDATE").get(0)[0], RequestStatus.APPROVED.getVal());
assertEquals(myJooqMock.getSqlBindings().get("UPDATE").get(0)[1], myUserRecord.getId());
assertEquals(myJooqMock.getSqlBindings().get("UPDATE").get(1)[0], PrivilegeLevel.PF.getVal());
assertEquals(myJooqMock.getSqlBindings().get("UPDATE").get(1)[1], myUserRecord.getId());

int getId()

Gets the ID of the next record to be inserted. Not used yet, but could be useful for debugging purposes.

DSLContext getContext()

This method initializes the mock database when passed into the constructor of a real implementation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Example from RequestsProcessorImplTest.java

// set up all the mocks
@BeforeEach
public void setup() {
  this.myJooqMock = new JooqMock();
  this.myRequestsProcessorImpl =
      new RequestsProcessorImpl(myJooqMock.getContext(), 
        new Emailer(myJooqMock.getContext()));
}

MockResult[] execute(MockExecuteContext ctx)

Executes a query on the mock. Whenever execute() is called in the implementation in what would be for a real database, it gets forwarded to this method instead during testing.