Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task(tests): introduce prebuilt mocks and mocking helpers #4197

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

bnjbvr
Copy link
Member

@bnjbvr bnjbvr commented Oct 31, 2024

This introduces new test utilities, starting with MatrixMockServer. This is a pair of a MockServer and a Client (one can retrieve them respectively with .server() and .client()).

It implements a few mock endpoints, expanding and limited the shared code as much as possible, so the mocks are still flexible to use as scoped/unscoped mounts, named, and so on.

It works like this:

  • start with telling which endpoint you'd like to mock, e.g. mock_room_send(). This returns a specialized MockSomething data structure, with its own impl. For this example, it's MockRoomSend.
  • configure the response on the endpoint-specific mock data structure. For instance, if you want the sending to result in a transient failure, call MockRoomSend::error500(); if you want it to succeed and return the event $42, call MockRoomSend::ok(event_id!("$42")). It's still possible to call respond_with(), as we do with wiremock MockBuilder, for maximum flexibility when the helpers aren't sufficient.
  • once the endpoint's response is configured, for any mock builder, you get a MatrixMock; this is a plain wiremock::Mock with the server curried, so one doesn't have to pass it around when calling .mount() or .mount_as_scoped(). As such, it mostly defers its implementations to wiremock::Mock under the hood.

Example

With this, we can go from that code:

#[async_test]
async fn test_make_reply_event_doesnt_require_event_cache() {
    let (client, server) = logged_in_client_with_server().await;

    let event_id = event_id!("$1");
    let resp_event_id = event_id!("$resp");
    let room_id = room_id!("!galette:saucisse.bzh");

    let f = EventFactory::new();

    let raw_original_event = f
        .text_msg("hi")
        .event_id(event_id)
        .sender(client.user_id().unwrap())
        .room(room_id)
        .into_raw_timeline();

    mock_sync_with_new_room(
        |builder| {
            builder.add_joined_room(JoinedRoomBuilder::new(room_id));
        },
        &client,
        &server,
        room_id,
    )
    .await;

    Mock::given(method("GET"))
        .and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/"))
        .and(header("authorization", "Bearer 1234"))
        .respond_with(ResponseTemplate::new(200).set_body_json(raw_original_event.json()))
        .expect(1)
        .named("/event")
        .mount(&server)
        .await;

    let new_content = RoomMessageEventContentWithoutRelation::text_plain("uh i mean bonjour");

    let room = client.get_room(room_id).unwrap();

    // make_edit_event works, even if the event cache hasn't been enabled.
    room.make_edit_event(resp_event_id, EditedContent::RoomMessage(new_content)).await.unwrap();
}

To that code:

#[async_test]
async fn test_make_reply_event_doesnt_require_event_cache() {
    let mock = MatrixMockServer::new().await;
    let user_id = mock.client().user_id().unwrap().to_owned();

    let room_id = room_id!("!galette:saucisse.bzh");
    let room = mock.sync_joined_room(room_id).await;

    let event_id = event_id!("$1");
    let f = EventFactory::new();
    mock.mock_room_event()
        .ok(f.text_msg("hi").event_id(event_id).sender(&user_id).room(room_id).into_timeline())
        .expect(1)
        .named("/event")
        .mount()
        .await;

    let new_content = RoomMessageEventContentWithoutRelation::text_plain("uh i mean bonjour");

    // make_edit_event works, even if the event cache hasn't been enabled.
    room.make_edit_event(event_id, EditedContent::RoomMessage(new_content)).await.unwrap();
}

@bnjbvr bnjbvr requested a review from a team as a code owner October 31, 2024 13:30
@bnjbvr bnjbvr requested review from andybalaam and removed request for a team October 31, 2024 13:30
Copy link

codecov bot commented Oct 31, 2024

Codecov Report

Attention: Patch coverage is 94.81481% with 7 lines in your changes missing coverage. Please review.

Project coverage is 84.94%. Comparing base (75683d2) to head (e9016b2).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
crates/matrix-sdk/src/test_utils/mocks.rs 94.69% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4197      +/-   ##
==========================================
+ Coverage   84.88%   84.94%   +0.05%     
==========================================
  Files         272      273       +1     
  Lines       29131    29265     +134     
==========================================
+ Hits        24729    24858     +129     
- Misses       4402     4407       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bnjbvr bnjbvr force-pushed the bnjbvr/prebuilt-mock-test-system branch from 2c8e654 to e9016b2 Compare October 31, 2024 14:17
Copy link
Contributor

@andybalaam andybalaam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Passing on to @richvdh because a) I think he will have helpful feedback and b) I'm not available next week.

}

impl MatrixMockServer {
/// Create a new mocked server specialized for Matrix usage.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"mocked server" -> "wiremock server"


/// Creates a new [`MatrixMockServer`] when both parts have been already
/// created.
pub fn from_pair(server: MockServer, client: Client) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from_pair -> from_server_and_client or from_parts ?

/// Overrides the sync/ endpoint with knowledge that the given
/// invited/joined/knocked/left room exists, runs a sync and returns the
/// given room.
pub async fn sync_room(&self, room_id: &RoomId, room_data: impl Into<AnyRoomBuilder>) -> Room {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to think of a more informative name for this...

Maybe create_or_change_room ? yuck.

self.sync_room(room_id, JoinedRoomBuilder::new(room_id)).await
}

pub async fn verify_and_reset(&self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally have no idea what these methods do, so would be nice to have a comment.

}

/// A wiremock Mock wrapping also the server, to not have to pass it around when
/// mounting.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this. Can you find a different way to write it? I think maybe it's because I don't know much about wiremock.

Copy link
Member

@richvdh richvdh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some thoughts. Looks like a great improvement.

use super::logged_in_client;
use crate::{Client, Room};

pub struct MatrixMockServer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to give this a doc-comment explaining its purpose and scope.

Possibly some of the words from your excellent PR description could stand to end up here, as examples.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

come to that, could you add documentation to all of the pub structs being added here?

}

// Specific mount endpoints.
impl MatrixMockServer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is dividing the impl into two bits like this idiomatic? It's confusing for me: I see this and think "hold on, if this is impl MatrixMockServer then what's the other bit?", and repeat a few times to check I'm not missing something.


pub struct MatrixMockServer {
server: MockServer,
client: Client,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including a client in MatrixMockServer feels a bit awkward to me. WDYT about making the caller responsible for instantiating the client (possibly by having client() instantiate a new Client) and, say, passing a client as an argument into MockSync::ok_and_run ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants