Skip to main content

Yaku Unit Tests

This is the guide for testing a StdPattern. A base pattern groups the tiles and hands them to a StdPattern, which outputs han/fu. So a StdPattern test receives already-grouped tiles (e.g. as 33332) and doesn't worry about grouping.

Grouping can change the result

The same tiles grouped differently can score differently — that's why you specify the groups explicitly in these tests.

Basics

Let's test an iipeikou (一盃口) hand:

[TestClass]
public class IipeikouTest {
protected StdPattern V { get; set; } = new Iipeikou(null);

[TestMethod]
public void TestResolved() {
new StdTestBuilder(V)
.AddFree("123s")
.AddFree("123s")
.AddFree("456m")
.AddFree("22m")
.AddAgari("23s", "4s")
.Resolve(true)
.ExpectScoring(ScoringType.Han, 1)
.NoMore();
}
}

First we construct the yaku under test:

protected StdPattern V { get; set; } = new Iipeikou(null);

Pass one null per constructor parameter. Don't worry — those arguments are unused in unit tests; at runtime the DI container supplies the real values.

TestResolved() tests this hand:

11222333s22456m+4s
Iipeikou, winning on 4s

It satisfies iipeikou, so we call Resolve(true) to confirm the pattern parses, then assert the output is 1 han with ExpectScoring(ScoringType.Han, 1) and no extra output via NoMore().

Hands that shouldn't match

To test a hand that shouldn't qualify, call Resolve(false) — you don't need to check scoring:

[TestMethod]
public void TestFailed() {
new StdTestBuilder(V)
.AddFree("123s")
.AddFree("123s")
.AddCalled("456m", 0)
.AddFree("22m")
.AddAgari("23s", "4s")
.Resolve(false);
}
11222333s22+-456m+4s
Open hand (456m called) → not iipeikou

AddCalled("456m", 0) specifies a called meld; the 0 says the tile at index 0 (the 4m) came from another player's discard. That makes the hand open, so it fails iipeikou (which requires a closed hand).

StdTestBuilder API

MethodPurpose
AddFree(tiles)Add a group of concealed hand tiles.
AddCalled(tiles, index)Add a called meld or ankan. index is the claimed tile's position; -1 means concealed (ankan).
AddAgari(group, winningTile)Add the group that contains the winning tile (its placement can affect the result, so it's specified explicitly).
Resolve(true/false)Assert the pattern does / doesn't match.
ExpectScoring(type, val)After a successful resolve, assert a han/fu output is present.
NoMore()Assert there are no scoring outputs beyond those already checked.
ForceMenzen(...)Force closed-hand status.
WithConfig(cfg => …)Modify the game config to test rule-dependent yaku.

ForceMenzen is rarely needed — menzen is inferred from the called melds by default.

Config-dependent yaku

Use WithConfig to change rules. For example, with open-tanyao disabled, an open hand can't be tanyao:

[TestMethod]
public void TestTanyaoFailed() {
new StdTestBuilder(V)
.WithConfig(config => config.agariOption &= ~AgariOption.Kuitan)
.AddCalled("234s", 0)
.AddFree("345p")
.AddFree("456m")
.AddFree("22m")
.AddAgari("23s", "4s")
.Resolve(false);
}

Advanced: mocking game components

StdTestBuilder creates partially mocked game components. Yaku tests generally don't need to touch them; if you do, check RabiRiichiTests/Helper/RabiMock.cs to see what's mocked.

Score corrections (pao)

Score corrections (OnScoreTransfer) are mainly used for pao (責任払い). Most yaku use the default implementation (return false) and need no test for it. There isn't a dedicated convenience API for this yet, so those cases require manual construction and assertions — contributions welcome.