Skip to content

Testing mid level API

Daniel Frantík edited this page May 13, 2026 · 1 revision

Testing with TikFakeConnection — Mid-level (ADO.NET-style) API

⚠️ Alpha APItik4net.testing is in early development. The builder methods and class names may change in future releases.

Package: tik4net.testing (NuGet) — add it to your test project only.
See also: Testing low-level API · Testing high-level API

TikFakeConnection intercepts at CallCommandSync. ITikCommand.Execute* methods call that internally, so they work exactly as with a real connection — the real parsing logic (scalar extraction, single-row enforcement, trap-to-exception conversion) runs unchanged.

Setup

using tik4net;
using tik4net.Testing;

Building entities with private fields: when you need to set read-only entity properties (.id, dynamic, …) use WithId / WithValue from TikFakeEntityExtensions — see Testing high-level API — Building fake entity instances.


ExecuteNonQuery

Commands that return only !done (e.g. /log/info, /system/reboot):

var conn = new TikFakeConnection()
    .WithNonQuery(rows => rows.First().StartsWith("/log/"));

ITikCommand cmd = conn.CreateCommand("/log/info",
    conn.CreateParameter("message", "test message"));
cmd.ExecuteNonQuery(); // succeeds

ExecuteScalar

Commands that return a single value — either =ret= in !done (e.g. /add) or a single field in a single !re row (e.g. /system/identity/print):

// /add → returns new id via =ret= in !done
var conn = new TikFakeConnection()
    .WithScalarResponse(
        predicate: rows => rows.First() == "/ip/firewall/address-list/add",
        value:     "*42");

ITikCommand cmd = conn.CreateCommandAndParameters(
    "/ip/firewall/address-list/add",
    "list",    "MY_LIST",
    "address", "10.0.0.1");
string newId = cmd.ExecuteScalar();
Assert.AreEqual("*42", newId);
// /system/identity/print → single-field !re row
var conn = new TikFakeConnection()
    .WithResponse(
        rows => rows.First() == "/system/identity/print",
        new ITikSentence[]
        {
            new TikFakeReSentence(new Dictionary<string, string> { { "name", "MyRouter" } }),
            new TikFakeDoneSentence(),
        });

string identity = conn.CreateCommand("/system/identity/print").ExecuteScalar();
Assert.AreEqual("MyRouter", identity);

Dynamic scalar — response depends on what was sent:

var conn = new TikFakeConnection()
    .WithScalarResponse(
        predicate:    rows => rows.First() == "/ip/address/add",
        valueFactory: rows =>
        {
            // derive fake id from the address parameter
            string addr = rows.FirstOrDefault(r => r.StartsWith("=address=")) ?? "=address=unknown";
            return addr.GetHashCode() > 0 ? "*10" : "*11";
        });

ExecuteList

Commands that return multiple !re rows:

var conn = new TikFakeConnection()
    .WithResponse(
        rows => rows.First() == "/ip/route/print",
        new ITikSentence[]
        {
            new TikFakeReSentence(new Dictionary<string, string>
                { { ".id", "*1" }, { "dst-address", "0.0.0.0/0" }, { "gateway", "192.168.1.1" } }),
            new TikFakeReSentence(new Dictionary<string, string>
                { { ".id", "*2" }, { "dst-address", "10.0.0.0/8" }, { "gateway", "10.0.0.1" } }),
            new TikFakeDoneSentence(),
        });

ITikCommand cmd = conn.CreateCommand("/ip/route/print");
IEnumerable<ITikReSentence> routes = cmd.ExecuteList();
Assert.AreEqual(2, routes.Count());
Assert.AreEqual("0.0.0.0/0", routes.First().GetResponseField("dst-address"));

ExecuteSingleRow

var conn = new TikFakeConnection()
    .WithResponse(
        rows => rows.First() == "/system/resource/print",
        new ITikSentence[]
        {
            new TikFakeReSentence(new Dictionary<string, string>
            {
                { "cpu-load",    "12"      },
                { "free-memory", "52428800" },
                { "version",     "7.14"    },
            }),
            new TikFakeDoneSentence(),
        });

ITikReSentence row = conn.CreateCommand("/system/resource/print").ExecuteSingleRow();
Assert.AreEqual("12", row.GetResponseField("cpu-load"));

ExecuteAsync

Async commands (torch, listen, …) call CallCommandAsync on a background thread. Register the sentences exactly as for sync — they will be delivered via callback:

var received = new List<string>();
var done = new ManualResetEventSlim(false);

var conn = new TikFakeConnection()
    .WithResponse(
        rows => rows.First() == "/tool/torch",
        new ITikSentence[]
        {
            new TikFakeReSentence(new Dictionary<string, string> { { "tx", "1000" }, { "rx", "500" } }),
            new TikFakeReSentence(new Dictionary<string, string> { { "tx", "2000" }, { "rx", "800" } }),
            new TikFakeDoneSentence(),
        });

ITikCommand torchCmd = conn.CreateCommand("/tool/torch",
    conn.CreateParameter("interface", "ether1"));

torchCmd.ExecuteAsync(
    oneResponseCallback: re    => received.Add(re.GetResponseField("tx")),
    onDoneCallback:      ()    => done.Set());

done.Wait(TimeSpan.FromSeconds(2));
Assert.AreEqual(new[] { "1000", "2000" }, received);

Trap → exception

TikCommandTrapException is thrown by all Execute* methods when a !trap is present:

var conn = new TikFakeConnection()
    .WithTrap(
        predicate: rows => rows.First() == "/ip/address/add",
        message:   "already have such item");

ITikCommand cmd = conn.CreateCommandAndParameters("/ip/address/add",
    "address", "10.0.0.1/24", "interface", "ether1");

Assert.ThrowsException<TikCommandTrapException>(() => cmd.ExecuteNonQuery());

Clone this wiki locally