Skip to content

Commit e11d615

Browse files
committed
Add unit tests for MarketDepth and MarketDepthManager classes
1 parent ff16611 commit e11d615

5 files changed

Lines changed: 431 additions & 3 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
cache: true
3838
cache-dependency-path: |
3939
src/**/*.csproj
40+
tests/**/*.csproj
4041
src/**/global.json
4142
src/**/NuGet.Config
4243
@@ -50,9 +51,9 @@ jobs:
5051
run: dotnet build --no-restore --configuration ${{ matrix.configuration }} --nologo
5152

5253
- name: Test
53-
run: dotnet test --no-build --configuration ${{ matrix.configuration }} --collect:"XPlat Code Coverage" --logger "trx;LogFileName=test_results.trx" || echo "No tests found"
54+
run: dotnet test BinanceBot.sln --no-build --configuration ${{ matrix.configuration }} --collect:"XPlat Code Coverage" --logger "trx;LogFileName=test_results.trx"
5455

55-
- name: Upload test results (TRX)
56+
- name: Upload test results
5657
if: always()
5758
uses: actions/upload-artifact@v4
5859
with:
@@ -61,7 +62,7 @@ jobs:
6162
**/TestResults/**/*.trx
6263
if-no-files-found: warn
6364

64-
- name: Upload code coverage (Cobertura)
65+
- name: Upload code coverage
6566
if: always()
6667
uses: actions/upload-artifact@v4
6768
with:

src/BinanceBot.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1414
..\README.md = ..\README.md
1515
EndProjectSection
1616
EndProject
17+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BinanceBot.Market.Tests", "..\tests\BinanceBot.Market.Tests\BinanceBot.Market.Tests.csproj", "{C2DCC04A-3BF3-4568-96D9-5E54B4952657}"
18+
EndProject
1719
Global
1820
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1921
Debug|Any CPU = Debug|Any CPU
@@ -32,6 +34,10 @@ Global
3234
{7912A437-0EC4-4939-B244-9379927AB0D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
3335
{7912A437-0EC4-4939-B244-9379927AB0D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
3436
{7912A437-0EC4-4939-B244-9379927AB0D1}.Release|Any CPU.Build.0 = Release|Any CPU
37+
{C2DCC04A-3BF3-4568-96D9-5E54B4952657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38+
{C2DCC04A-3BF3-4568-96D9-5E54B4952657}.Debug|Any CPU.Build.0 = Debug|Any CPU
39+
{C2DCC04A-3BF3-4568-96D9-5E54B4952657}.Release|Any CPU.ActiveCfg = Release|Any CPU
40+
{C2DCC04A-3BF3-4568-96D9-5E54B4952657}.Release|Any CPU.Build.0 = Release|Any CPU
3541
EndGlobalSection
3642
GlobalSection(SolutionProperties) = preSolution
3743
HideSolutionNode = FALSE
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
13+
<PackageReference Include="Moq" Version="4.20.72" />
14+
<PackageReference Include="xunit" Version="2.9.2" />
15+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="..\..\src\BinanceBot.Market\BinanceBot.Market.csproj" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<Using Include="Xunit" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
using Binance.Net.Enums;
2+
using Binance.Net.Objects.Models;
3+
using BinanceBot.Market.Core;
4+
5+
namespace BinanceBot.Market.Tests.Core;
6+
7+
public class MarketDepthTests
8+
{
9+
[Fact]
10+
public void Constructor_WithValidSymbol_CreatesInstance()
11+
{
12+
// Arrange & Act
13+
var marketDepth = new MarketDepth("BTCUSDT");
14+
15+
// Assert
16+
Assert.Equal("BTCUSDT", marketDepth.Symbol);
17+
Assert.Null(marketDepth.LastUpdateTime);
18+
Assert.Empty(marketDepth.Asks);
19+
Assert.Empty(marketDepth.Bids);
20+
}
21+
22+
[Theory]
23+
[InlineData("")]
24+
public void Constructor_WithInvalidSymbol_ThrowsArgumentException(string symbol)
25+
{
26+
// Act & Assert
27+
Assert.Throws<ArgumentException>(() => new MarketDepth(symbol));
28+
}
29+
30+
[Fact]
31+
public void UpdateDepth_WithValidData_UpdatesOrderBook()
32+
{
33+
// Arrange
34+
var marketDepth = new MarketDepth("BTCUSDT");
35+
var asks = new List<BinanceOrderBookEntry>
36+
{
37+
new() { Price = 50000m, Quantity = 1.5m },
38+
new() { Price = 50100m, Quantity = 2.0m }
39+
};
40+
var bids = new List<BinanceOrderBookEntry>
41+
{
42+
new() { Price = 49900m, Quantity = 1.0m },
43+
new() { Price = 49800m, Quantity = 0.5m }
44+
};
45+
46+
// Act
47+
marketDepth.UpdateDepth(asks, bids, 123456);
48+
49+
// Assert
50+
Assert.Equal(123456, marketDepth.LastUpdateTime);
51+
Assert.Equal(2, marketDepth.Asks.Count());
52+
Assert.Equal(2, marketDepth.Bids.Count());
53+
Assert.Equal(50000m, marketDepth.BestAsk.Price);
54+
Assert.Equal(49900m, marketDepth.BestBid.Price);
55+
}
56+
57+
[Fact]
58+
public void UpdateDepth_WithOldUpdateTime_IgnoresUpdate()
59+
{
60+
// Arrange
61+
var marketDepth = new MarketDepth("BTCUSDT");
62+
var asks = new List<BinanceOrderBookEntry>
63+
{
64+
new() { Price = 50000m, Quantity = 1.5m }
65+
};
66+
var bids = new List<BinanceOrderBookEntry>
67+
{
68+
new() { Price = 49900m, Quantity = 1.0m }
69+
};
70+
marketDepth.UpdateDepth(asks, bids, 123456);
71+
72+
// Act - try to update with older timestamp
73+
var newAsks = new List<BinanceOrderBookEntry>
74+
{
75+
new() { Price = 51000m, Quantity = 2.0m }
76+
};
77+
marketDepth.UpdateDepth(newAsks, bids, 123400);
78+
79+
// Assert - should still have old data
80+
Assert.Equal(123456, marketDepth.LastUpdateTime);
81+
Assert.Equal(50000m, marketDepth.BestAsk.Price);
82+
}
83+
84+
[Fact]
85+
public void UpdateDepth_RemovesPriceLevelWithZeroQuantity()
86+
{
87+
// Arrange
88+
var marketDepth = new MarketDepth("BTCUSDT");
89+
var asks = new List<BinanceOrderBookEntry>
90+
{
91+
new() { Price = 50000m, Quantity = 1.5m },
92+
new() { Price = 50100m, Quantity = 2.0m }
93+
};
94+
var bids = new List<BinanceOrderBookEntry>
95+
{
96+
new() { Price = 49900m, Quantity = 1.0m }
97+
};
98+
marketDepth.UpdateDepth(asks, bids, 123456);
99+
100+
// Act - update with zero quantity to remove price level
101+
var updateAsks = new List<BinanceOrderBookEntry>
102+
{
103+
new() { Price = 50000m, Quantity = 0m }
104+
};
105+
marketDepth.UpdateDepth(updateAsks, bids, 123457);
106+
107+
// Assert
108+
Assert.Single(marketDepth.Asks);
109+
Assert.Equal(50100m, marketDepth.BestAsk.Price);
110+
}
111+
112+
[Fact]
113+
public void BestPair_WhenOrderBookIsEmpty_ReturnsNull()
114+
{
115+
// Arrange
116+
var marketDepth = new MarketDepth("BTCUSDT");
117+
118+
// Act & Assert
119+
Assert.Null(marketDepth.BestPair);
120+
}
121+
122+
[Fact]
123+
public void BestPair_WhenOrderBookHasData_ReturnsPair()
124+
{
125+
// Arrange
126+
var marketDepth = new MarketDepth("BTCUSDT");
127+
var asks = new List<BinanceOrderBookEntry>
128+
{
129+
new() { Price = 50000m, Quantity = 1.5m }
130+
};
131+
var bids = new List<BinanceOrderBookEntry>
132+
{
133+
new() { Price = 49900m, Quantity = 1.0m }
134+
};
135+
marketDepth.UpdateDepth(asks, bids, 123456);
136+
137+
// Act
138+
var bestPair = marketDepth.BestPair;
139+
140+
// Assert
141+
Assert.NotNull(bestPair);
142+
Assert.Equal(50000m, bestPair.Ask.Price);
143+
Assert.Equal(49900m, bestPair.Bid.Price);
144+
Assert.Equal(100m, bestPair.PriceSpread);
145+
}
146+
147+
[Fact]
148+
public void MarketDepthChanged_RaisesEvent_WhenDepthUpdated()
149+
{
150+
// Arrange
151+
var marketDepth = new MarketDepth("BTCUSDT");
152+
MarketDepthChangedEventArgs? eventArgs = null;
153+
marketDepth.MarketDepthChanged += (sender, e) => eventArgs = e;
154+
155+
var asks = new List<BinanceOrderBookEntry>
156+
{
157+
new() { Price = 50000m, Quantity = 1.5m }
158+
};
159+
var bids = new List<BinanceOrderBookEntry>
160+
{
161+
new() { Price = 49900m, Quantity = 1.0m }
162+
};
163+
164+
// Act
165+
marketDepth.UpdateDepth(asks, bids, 123456);
166+
167+
// Assert
168+
Assert.NotNull(eventArgs);
169+
Assert.Equal(123456, eventArgs.UpdateTime);
170+
Assert.Single(eventArgs.Asks);
171+
}
172+
173+
[Fact]
174+
public void MarketBestPairChanged_RaisesEvent_WhenBestPairChanges()
175+
{
176+
// Arrange
177+
var marketDepth = new MarketDepth("BTCUSDT");
178+
var eventRaised = false;
179+
marketDepth.MarketBestPairChanged += (sender, e) => eventRaised = true;
180+
181+
var asks = new List<BinanceOrderBookEntry>
182+
{
183+
new() { Price = 50000m, Quantity = 1.5m }
184+
};
185+
var bids = new List<BinanceOrderBookEntry>
186+
{
187+
new() { Price = 49900m, Quantity = 1.0m }
188+
};
189+
190+
// Act
191+
marketDepth.UpdateDepth(asks, bids, 123456);
192+
193+
// Assert
194+
Assert.True(eventRaised);
195+
}
196+
197+
[Fact]
198+
public void Asks_AreSortedAscending()
199+
{
200+
// Arrange
201+
var marketDepth = new MarketDepth("BTCUSDT");
202+
var asks = new List<BinanceOrderBookEntry>
203+
{
204+
new() { Price = 50100m, Quantity = 2.0m },
205+
new() { Price = 50000m, Quantity = 1.5m },
206+
new() { Price = 50200m, Quantity = 1.0m }
207+
};
208+
var bids = new List<BinanceOrderBookEntry>
209+
{
210+
new() { Price = 49900m, Quantity = 1.0m }
211+
};
212+
213+
// Act
214+
marketDepth.UpdateDepth(asks, bids, 123456);
215+
216+
// Assert
217+
var askPrices = marketDepth.Asks.Select(a => a.Price).ToList();
218+
Assert.Equal(new[] { 50000m, 50100m, 50200m }, askPrices);
219+
}
220+
221+
[Fact]
222+
public void Bids_AreSortedDescending()
223+
{
224+
// Arrange
225+
var marketDepth = new MarketDepth("BTCUSDT");
226+
var asks = new List<BinanceOrderBookEntry>
227+
{
228+
new() { Price = 50000m, Quantity = 1.0m }
229+
};
230+
var bids = new List<BinanceOrderBookEntry>
231+
{
232+
new() { Price = 49800m, Quantity = 0.5m },
233+
new() { Price = 49900m, Quantity = 1.0m },
234+
new() { Price = 49700m, Quantity = 2.0m }
235+
};
236+
237+
// Act
238+
marketDepth.UpdateDepth(asks, bids, 123456);
239+
240+
// Assert
241+
var bidPrices = marketDepth.Bids.Select(b => b.Price).ToList();
242+
Assert.Equal(new[] { 49900m, 49800m, 49700m }, bidPrices);
243+
}
244+
245+
[Fact]
246+
public void UpdateDepth_WithZeroOrNegativeUpdateTime_ThrowsArgumentOutOfRangeException()
247+
{
248+
// Arrange
249+
var marketDepth = new MarketDepth("BTCUSDT");
250+
var asks = new List<BinanceOrderBookEntry>
251+
{
252+
new() { Price = 50000m, Quantity = 1.5m }
253+
};
254+
255+
// Act & Assert
256+
Assert.Throws<ArgumentOutOfRangeException>(() => marketDepth.UpdateDepth(asks, null, 0));
257+
Assert.Throws<ArgumentOutOfRangeException>(() => marketDepth.UpdateDepth(asks, null, -1));
258+
}
259+
}

0 commit comments

Comments
 (0)