Skip to content

Repository files navigation

Robot Framework MongoDBLibrary

PyPI Python versions CI License: MIT

MongoDBLibrary is a test library for Robot Framework that provides keywords for interacting with MongoDB databases.

📖 Keyword documentation — every keyword, its arguments and examples, published per version.

Features

  • Connect to a single host, a connection string, or a hosted cluster such as MongoDB Atlas
  • Named connections with a connection pool, and clients shared between aliases
  • CRUD on one or many documents, with MongoDB query and update operators
  • Seed documents read from Extended JSON files: template placeholders filled from the call's arguments, suite variables substituted, and any field overridable by its path
  • Queries with projection, sorting, limiting, skipping and distinct values
  • Upserting and whole-document replacement, so a fixture step can run twice
  • Collection and database management: create, drop, list
  • Index creation, listing and dropping, including unique, sparse and TTL indexes
  • Retrying assertions on a query result, a document count, a set of values, or the existence of a document, a collection or an index — by its fields as well as its name
  • Explain Query for the query that returns nothing and reports no error: the plan the server chose, and the values it actually searched for
  • Run Database Command for everything the keywords do not wrap
  • Runs on Robot Framework 5.0 through 7.x, from one code path

Documentation

The keyword documentation describes every keyword, its arguments and examples. It is published per version, so you can read the documentation for the version you actually have installed rather than for whatever is newest:

The pages are generated from the library itself when a tag or a push to main is published, so they cannot drift from the code they document. The releases before 1.0.0 are not published there; the changelog covers what they were.

Installation

pip install robotframework-mongodb

Or with Poetry:

poetry add robotframework-mongodb

Requirements

Supported
Robot Framework 5.0 – 7.x
Python 3.10 – 3.14
MongoDB any version pymongo 4 speaks to (3.6 and later)

Everything in the supported range is exercised in CI, not merely allowed by the version constraint. If you are on an older Robot Framework, read Older Robot Framework Versions — one keyword argument behaves differently and the rest is identical.

Three names differ and are easy to mix up: the package you install is robotframework-mongodb, the library you import is MongoDBLibrary, and the repository is robotframework-mongodblibrary.

Importing

*** Settings ***
Library    MongoDBLibrary    coerce_object_ids=${True}

There are two import-time arguments, and everything else — hosts, credentials, TLS, auth mechanism — is configured per connection, on the connect keywords.

coerce_object_ids (default ${True}) controls whether a string _id in a query is rewritten to a BSON ObjectId; see Document Ids for what that means and when to turn it off.

document_path (unset by default) is the directory that documents given to Load Document and Insert Document From File by file name are looked up in; see Documents From Files. It removes the repetition of naming the directory in every call and does nothing else, so a path given to the keyword still works without it.

*** Settings ***
Library    MongoDBLibrary    document_path=${CURDIR}/documents

The library's scope is GLOBAL, so one instance is shared by every suite in a run and a connection opened in one suite is still open in the next. One consequence is worth knowing: Robot Framework creates a separate instance per set of import arguments, so two suites that import with different argument values get separate instances, and therefore separate connection pools rather than shared connections.

Usage Example

Keywords take named arguments:

*** Settings ***
Library    MongoDBLibrary

*** Test Cases ***
Connect With A Host And Credentials
    Connect To Database    db_name=mydb    db_user=${DB_USER}    db_password=${DB_PASSWORD}
    ...                    db_host=localhost    db_port=27017
    ${doc_id}              Insert Document    collection_name=mycollection    document={"key": "value"}
    ${document}            Find Document      collection_name=mycollection    key=value
    [Teardown]             Disconnect From Database

Connect With A Connection String
    Connect To Database Using Connection String    db_conn_string=${DB_CONNECT_STRING}    db_name=mydb
    ${count}               Count Documents    collection_name=mycollection    key=value
    [Teardown]             Disconnect From Database

Several Databases At Once

Give each connection an alias and the connections stay open side by side. Keywords use the active connection unless passed an alias of their own, and Switch Connection changes which one that is:

*** Test Cases ***
Copy A Document Between Two Databases
    Connect To Database    db_name=source    db_host=localhost    alias=source
    Connect To Database    db_name=target    db_host=localhost    alias=target
    ${document}    Find Document    collection_name=orders    order_id=A-1    alias=source
    Switch Connection    alias=target
    Insert Document      collection_name=orders    document=${document}
    [Teardown]           Disconnect From All Databases

Keeping Credentials Out Of The Log

Pass credentials as variables rather than writing them into the suite, because Robot Framework copies the argument as written into the log.

On Robot Framework 7.4 and later, db_password and db_conn_string accept a Secret, which keeps the value out of log.html and output.xml:

*** Variables ***
${DB_PASSWORD: Secret}    %{MONGO_PASSWORD}

*** Test Cases ***
Connect With A Secret
    Connect To Database    db_name=mydb    db_user=${DB_USER}    db_password=${DB_PASSWORD}
    ...                    db_host=localhost

A secret can only come from the environment — Robot Framework refuses to build one from a literal, so the value cannot be written into the suite by accident. It is not encryption: the value is plain text in memory and is sent to MongoDB as typed. What it prevents is Robot Framework recording the argument.

On Robot Framework 5.0 through 7.3 there is no Secret type, so both arguments take a plain string and nothing above applies. Nothing else differs; see Older Robot Framework Versions.

Resetting Between Tests

Delete All Documents From Collection removes the documents but leaves everything defined on the collection — its indexes and options. So a unique index created by one test still rejects the next test's fixtures. Drop Collection removes the collection itself, which is what actually resets it, and succeeds when the collection is not there:

*** Test Cases ***
Reset The Collection Completely
    [Teardown]    Drop Collection    collection_name=orders

To write a setup step that can run twice, upsert rather than insert:

*** Keywords ***
Ensure The Test User Exists
    Update Document    collection_name=users    query={"email": "a@example.test"}
    ...                update={"active": ${True}}    upsert=${True}

Waiting For Data

The assertion keywords retry, which is the part a suite gets wrong when it hand-rolls the wait around Find Document:

*** Test Cases ***
Wait For The Order To Be Written
    Document Should Exist    collection_name=orders    order_id=A-1    retry_timeout=10 seconds
    Check Distinct Values    collection_name=orders    field=status
    ...                      assertion_operator=not contains    expected_value=pending

Document Ids

This library rewrites the _id in your queries. MongoDB stores _id as a BSON ObjectId, not a string, and the two never match each other. Robot Framework stores variables as text, so an id that has been through a variable, a file or an API response arrives as a string and would silently match nothing — no error, just an empty result.

So before a query is sent, an _id that is a string of 24 hexadecimal characters is converted to an ObjectId:

_id="6a7ccdea6abf6a4ebbc3514f"   ->   _id=ObjectId("6a7ccdea6abf6a4ebbc3514f")

Values inside $in and comparison operators are converted too. Anything that is not a valid ObjectId (user-42, order_991) is passed through untouched, as is every field other than _id, every document you insert, and every aggregation pipeline.

Turn it off if your collections use string _ids that happen to be 24 hex characters, such as a truncated hash — the conversion would look for an ObjectId that does not exist:

*** Settings ***
Library    MongoDBLibrary    coerce_object_ids=${False}

*** Test Cases ***
Query An Id Explicitly
    ${oid}    Convert To Object Id    ${doc_id}
    Find Document    collection_name=orders    _id=${oid}

Full details, including exactly what is and is not rewritten, are in the Object Ids section of the keyword documentation.

Diagnosing An Empty Result

An id that does not match is one cause of a query that finds nothing and errors on nothing. Explain Query covers the rest: it asks the server how it answered the query, and the field to read first is index_bounds — the values it actually searched for.

*** Test Cases ***
Find Out Why The Document Is Missing
    ${plan}    Explain Query    collection_name=readings    _id.deviceId=${device_id}    _id.date=${date}
    Log    ${plan.index_bounds}
'_id.deviceId': ['["device-1", "device-1"]']
'_id.date':     ['[new Date(1767830400000), new Date(1767830400000)]']

Comparing that with what the suite passed is usually the whole diagnosis. The keyword asserts nothing and is not meant to stay in a passing test: put it beside the find that returned nothing, read the log, take it out again.

A compound _id

A collection keyed by a subdocument rather than a single value hits three of these at once, and none of them errors:

{"_id": {"deviceId": "device-1", "date": ISODate("2026-01-08T00:00:00.001Z")}}
  1. The automatic _id_ index cannot answer a query on part of the id. It stores the subdocument as one opaque value, so _id.deviceId and _id.date are served by a separate index if one exists, and by reading every document if not. Nothing in the suite says that index is load-bearing, so assert it:

    Collection Should Have Index    collection_name=readings    keys={"_id.deviceId": 1, "_id.date": 1}
  2. Matching the whole _id is field-order sensitive. It compares the stored BSON, so the order of the fields is part of the value:

    query={"_id": {"deviceId": "device-1", "date": ${date}}}    # matches
    query={"_id": {"date": ${date}, "deviceId": "device-1"}}    # matches nothing, silently
  3. A date compares exactly. A datetime at midnight does not match a document stored with milliseconds — which is exactly what the index_bounds above make visible.

Explain Query also reports collection_scan, and it is deliberately information rather than an assertion. MongoDB rightly chooses a collection scan on a small collection, where reading it beats an index lookup plus a fetch, so "this query must not scan" passes against production-sized data and fails against a freshly seeded test collection with nothing wrong. Where a suite needs an index, Collection Should Have Index says so directly and cannot flake.

Documents From Files

A fixture document written into a suite is fine until a second test needs it, and then it is copied and the copies drift. Load Document reads one from a JSON file, and Insert Document From File reads it and inserts it in one step:

*** Settings ***
Library    MongoDBLibrary    document_path=${CURDIR}/documents

*** Test Cases ***
Seed An Order
    ${document}    Load Document    order.json
    ${doc_id}      Insert Document From File    collection_name=orders    path=order.json

The file is MongoDB Extended JSON, so it can hold the types MongoDB stores rather than only the ones JSON has syntax for:

{
    "_id": {"$oid": "6a7ccdea6abf6a4ebbc3514f"},
    "placedAt": {"$date": "2026-03-01T09:30:00Z"},
    "quantity": {"$numberInt": "3"},
    "total": {"$numberDouble": "42.50"},
    "email": "${EMAIL}",
    "lines": [{"sku": "A-1", "quantity": 2}]
}

That matters for the same reason Document Ids does: a string that looks like an id does not match one, and a date written as text is stored as text and does not compare as a date. Plain JSON values keep their own types. MongoDB's update operators are $-prefixed too and are passed through untouched, nested values included, so a file can hold {"$set": ..., "$push": ...} for an update as readily as a document to insert.

${...} in the file is replaced from the variables the calling suite can see, and one that resolves to nothing fails the keyword naming the file and the variable — rather than inserting a document that still says ${EMAIL} and failing a test somewhere later against data that looks almost right.

Filling A Template

A value that differs on every call belongs in the call rather than in a suite variable. A file can declare a hole for one, written {name} and filled from the keyword's named arguments:

{
    "unique_id": "{unique_id}",
    "customerId": "{customerId}",
    "placedAt": "{placed_at}",
    "quantity": "{quantity}",
    "reference": "REF-{unique_id}",
    "status": "new"
}
*** Test Cases ***
Seed An Order Per Call
    ${doc_id}    Insert Document From File    collection_name=orders    path=order.json
    ...          unique_id=order-1    customerId=${oid}    placed_at=${now}    quantity=3
    ${document}  Load Document    order.json    &{placeholders}

The template stays valid JSON, so editors, jq and formatters still read it. Two kinds of hole, and the syntax says which is which: ${name} comes from the suite, {name} from the call.

A string that is exactly one placeholder is replaced whole, quotes included, by the value's own Extended JSON form — which is what lets a valid-JSON template carry a value JSON cannot write, with no $oid or $date wrapper needed:

In the file Given Stored as
"customerId": "{customerId}" an ObjectId a real ObjectId
"placedAt": "{placed_at}" a datetime a real datetime
"quantity": "{quantity}" 3 a real int
"reference": "REF-{unique_id}" order-1 "REF-order-1"

Inside a string, {{ and }} are literal braces as in str.format; JSON's own braces are never touched. A hole the file declares that no argument fills is an error naming the file and the holes, for the same reason an unresolved ${...} is.

Overriding Fields

A field the file already fills can be changed without declaring a hole for it, which is what a value that varies only occasionally wants — the file's own value stays as the default for every test that does not mention it. Any field can be overridden by its path, with list positions written as numbers:

*** Test Cases ***
Seed Two Orders From One File
    ${shipped}    Load Document    order.json    status=shipped    lines.0.quantity=3
    ${mine}       Load Document    order.json    customer._id=${customer_id}

This is the part a suite cannot do for itself: &{dict} expansion merges one level deep, so overriding a nested field otherwise means rebuilding every level above it. A value written literally is read the way the file's own values are — 0.8 is a number, true is a boolean, {"$oid": "..."} is an ObjectId, and a word such as shipped is text. Every step of a path has to exist in the document already; one that does not fails with what the document held at that point, because a path that misses is a typo far more often than it is a field meant to be added.

Placeholders and overrides are given the same way and the file decides which an argument is: a name it declares as a placeholder fills that placeholder, anything else is a path. A bare name that is neither fails naming both, since which was meant decides whether the fix belongs in the file or in the call.

Connecting To A Hosted Cluster (MongoDB Atlas)

A hosted cluster's name is a DNS seed list rather than a single host, so it needs mongodb+srv resolution and TLS. Either use the connection string keyword with the mongodb+srv:// URI from your provider, or set srv=${True}:

*** Test Cases ***
Connect To Atlas With A Connection String
    Connect To Database Using Connection String
    ...    db_conn_string=${DB_CONNECT_STRING}    db_name=mydb

Connect To Atlas With A Cluster Name
    Connect To Database    db_name=mydb    db_user=${DB_USER}    db_password=${DB_PASSWORD}
    ...                    db_host=mycluster.abcde.mongodb.net    srv=${True}

db_port is ignored when srv is enabled, because the seed list supplies its own ports. Use tls=${False} to force TLS off, or tls=${True} to force it on for a plain host.

Using With AWS

Two separate things are often confused here. Amazon DocumentDB is a MongoDB-compatible service whose transport needs particular settings; AWS IAM authentication is a credential mechanism, usable against DocumentDB 5.0 and later and against Atlas clusters hosted on AWS. They are configured independently, and you may want one, the other, or both.

Neither path is exercised in CI, unlike the Robot Framework range — they need AWS infrastructure. The library passes these options straight to pymongo and contains no AWS-specific code.

Amazon DocumentDB

DocumentDB requires TLS against Amazon's own certificate authority, and it does not implement retryable writes, which pymongo enables by default. Leaving retryWrites on is the usual first failure. tlsCAFile has no keyword argument, so DocumentDB is the one case where the connection string is the only route:

*** Variables ***
${DB_CONNECT_STRING}    mongodb://${DB_USER}:${DB_PASSWORD}@mycluster.cluster-abc123.eu-west-1.docdb.amazonaws.com:27017/?tls=true&tlsCAFile=global-bundle.pem&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false

*** Test Cases ***
Connect To DocumentDB
    Connect To Database Using Connection String
    ...    db_conn_string=${DB_CONNECT_STRING}    db_name=mydb
    [Teardown]    Disconnect From Database
  • tls=true&tlsCAFile=global-bundle.pem — download the bundle from Amazon's CA page and give the path to it.
  • retryWrites=false — required; without it every write fails.
  • replicaSet=rs0&readPreference=secondaryPreferred — for a cluster endpoint. Drop both when connecting to a single instance endpoint.

Connect To Database reaches tls, replica_set and read_preference as named arguments, but not tlsCAFile, so it only suits a DocumentDB cluster whose CA is already trusted by the system store.

AWS IAM Authentication (MONGODB-AWS)

IAM authentication needs two options set together — the mechanism, and an auth source of $external. Install the mechanism's dependency with the aws extra:

pip install "robotframework-mongodb[aws]"
poetry add robotframework-mongodb --extras aws

That adds one dependency, pymongo-auth-aws. Then set the mechanism, either with named arguments:

*** Test Cases ***
Connect With An IAM Role
    Connect To Database    db_name=mydb    db_host=mycluster.abcde.mongodb.net
    ...                    srv=${True}    auth_mechanism=MONGODB-AWS    auth_source=$external
    [Teardown]             Disconnect From Database

or as URI parameters, which is how you combine IAM with the DocumentDB settings above — append &authMechanism=MONGODB-AWS&authSource=$external to the connection string.

Credentials are resolved by pymongo-auth-aws, not by this library. Running on EC2, ECS, EKS or Lambda, the instance or task role is picked up with no credentials given at all — which is the point of using IAM. Otherwise pymongo-auth-aws reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and optionally AWS_SESSION_TOKEN from the environment. For an IAM user you can instead pass the access key as db_user and the secret key as db_password.

DocumentDB supports IAM only on version 5.0 and later. See pymongo's authentication examples, Atlas AWS IAM authentication, and AWS's IAM authentication guide.

Beyond These Keywords

The keywords cover what a suite normally needs. Run Database Command reaches everything else — server statistics, storage sizes, query plans and administrative commands are all database commands:

*** Test Cases ***
Read A Query Plan
    ${plan}    Run Database Command
    ...        command={"explain": {"find": "orders", "filter": {"status": "new"}}}

Transactions and sessions, change streams, GridFS and client-side field level encryption are deliberately not wrapped, because none of them fit a synchronous keyword taken one at a time. Use pymongo directly if a suite needs those.

Older Robot Framework Versions

The library supports Robot Framework 5.0 and later from a single code path — there is no separate release or compatibility shim to install. Installing it alongside an older Robot Framework is enough:

pip install robotframework-mongodb "robotframework==6.1.1"

The one dependency that needs pairing is the assertion engine, which backs the retrying assertion keywords. Its 3.x line requires Robot Framework 6.1.1, so on 5.0 through 6.0 pip needs to be told to take 2.x:

pip install robotframework-mongodb "robotframework==5.0.1" "robotframework-assertion-engine==2.0.0"

verify_assertion and AssertionOperator are the same in both lines, so every assertion keyword — the operators it accepts, the retrying, the failure messages — behaves the same either way.

What differs on an older version

Only one thing: Secret. On 7.4 and later, db_password and db_conn_string accept one, and the value is logged as <secret>. Below 7.4 the type does not exist, Robot Framework has no syntax to build one, and both arguments take a plain string — which is how they worked before 7.4 anyway. The library imports Secret conditionally and builds the argument's type from what is available, so nothing raises and nothing needs a version check in your suite.

Everything else is unaffected. The rest of what the library imports from robot (logger, the @keyword decorator, BuiltIn, DotDict, timestr_to_secs) long predates 5.0, and the keyword signatures use annotations 5.0 already converts.

Robot Framework 4 and older

Not supported: the assertion engine will not install below 5.0, and 4.x does not convert the built-in generic annotations the keywords use (list[str], dict[str, Any]), so arguments would arrive as strings.

How the range is tested

CI runs the unit tests and generates the keyword documentation against Robot Framework 5.0.1, 6.1.1, 7.3.2 and 7.4.0 — the floor, the version where the assertion engine changes line, and both sides of the Secret branch. Libdoc runs too, because it reads every signature and docstring and so catches an annotation an older version cannot convert, which the unit tests would not notice. A nightly job additionally installs whatever Robot Framework and assertion engine are newest on PyPI, ignoring the upper bounds in pyproject.toml, so a release that breaks the library shows up here rather than in your suite.

License

MIT

About

Robot Framework test library for MongoDB, built on pymongo 4. Named connections, query and update operators, indexes, and retrying assertions.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages