diff --git a/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs b/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs
index a6b1fa9e..fb43a9f4 100644
--- a/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Scenarios/ScenarioStateModel.cs
@@ -24,5 +24,10 @@
/// Gets or sets a value indicating whether this is finished.
///
public bool Finished { get; set; }
+
+ ///
+ /// Gets or sets the state counter.
+ ///
+ public int Counter { get; set; }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs
index ca822beb..7848b3df 100644
--- a/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs
+++ b/src/WireMock.Net.Abstractions/Server/IWireMockServer.cs
@@ -27,9 +27,9 @@ namespace WireMock.Server
///
IEnumerable MappingModels { get; }
- ///
- /// Gets the mappings.
- ///
+ //
+ // Gets the mappings.
+ //
//[PublicAPI]
//IEnumerable Mappings { get; }
diff --git a/src/WireMock.Net/IMapping.cs b/src/WireMock.Net/IMapping.cs
index 71d93743..a28bd526 100644
--- a/src/WireMock.Net/IMapping.cs
+++ b/src/WireMock.Net/IMapping.cs
@@ -51,6 +51,12 @@ namespace WireMock
[CanBeNull]
string NextState { get; }
+ ///
+ /// The number of times this match should be matched before the state will be changed to the next state.
+ ///
+ [CanBeNull]
+ int? StateTimes { get; }
+
///
/// The Request matcher.
///
diff --git a/src/WireMock.Net/Mapping.cs b/src/WireMock.Net/Mapping.cs
index 343c5efd..6c215be0 100644
--- a/src/WireMock.Net/Mapping.cs
+++ b/src/WireMock.Net/Mapping.cs
@@ -33,6 +33,9 @@ namespace WireMock
///
public string NextState { get; }
+ ///
+ public int? StateTimes { get; }
+
///
public IRequestMatcher RequestMatcher { get; }
@@ -64,9 +67,10 @@ namespace WireMock
/// The scenario. [Optional]
/// State in which the current mapping can occur. [Optional]
/// The next state which will occur after the current mapping execution. [Optional]
+ /// Only when the current state is executed this number, the next state which will occur. [Optional]
public Mapping(Guid guid, [CanBeNull] string title, [CanBeNull] string path,
[NotNull] IWireMockServerSettings settings, [NotNull] IRequestMatcher requestMatcher, [NotNull] IResponseProvider provider,
- int priority, [CanBeNull] string scenario, [CanBeNull] string executionConditionState, [CanBeNull] string nextState)
+ int priority, [CanBeNull] string scenario, [CanBeNull] string executionConditionState, [CanBeNull] string nextState, [CanBeNull] int? stateTimes)
{
Guid = guid;
Title = title;
@@ -78,6 +82,7 @@ namespace WireMock
Scenario = scenario;
ExecutionConditionState = executionConditionState;
NextState = nextState;
+ StateTimes = stateTimes;
}
///
diff --git a/src/WireMock.Net/Owin/MappingMatcher.cs b/src/WireMock.Net/Owin/MappingMatcher.cs
index 681b1570..71b4d814 100644
--- a/src/WireMock.Net/Owin/MappingMatcher.cs
+++ b/src/WireMock.Net/Owin/MappingMatcher.cs
@@ -23,12 +23,12 @@ namespace WireMock.Owin
{
try
{
- string scenario = mapping.Scenario != null && _options.Scenarios.ContainsKey(mapping.Scenario) ? _options.Scenarios[mapping.Scenario].NextState : null;
+ string nextState = GetNextState(mapping);
mappings.Add(new MappingMatcherResult
{
Mapping = mapping,
- RequestMatchResult = mapping.GetRequestMatchResult(request, scenario)
+ RequestMatchResult = mapping.GetRequestMatchResult(request, nextState)
});
}
catch (Exception ex)
@@ -56,5 +56,18 @@ namespace WireMock.Owin
return (match, partialMatch);
}
+
+ private string GetNextState(IMapping mapping)
+ {
+ // If the mapping does not have a scenario or _options.Scenarios does not contain this scenario from the mapping,
+ // just return null to indicate that there is no next state.
+ if (mapping.Scenario == null || !_options.Scenarios.ContainsKey(mapping.Scenario))
+ {
+ return null;
+ }
+
+ // Else just return the next state
+ return _options.Scenarios[mapping.Scenario].NextState;
+ }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs
index 9cf01318..cfc708cf 100644
--- a/src/WireMock.Net/Owin/WireMockMiddleware.cs
+++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs
@@ -79,7 +79,7 @@ namespace WireMock.Owin
{
foreach (var mapping in _options.Mappings.Values.Where(m => m?.Scenario != null))
{
- // Set start
+ // Set scenario start
if (!_options.Scenarios.ContainsKey(mapping.Scenario) && mapping.IsStartState)
{
_options.Scenarios.TryAdd(mapping.Scenario, new ScenarioState
@@ -122,9 +122,7 @@ namespace WireMock.Owin
if (targetMapping.Scenario != null)
{
- _options.Scenarios[targetMapping.Scenario].NextState = targetMapping.NextState;
- _options.Scenarios[targetMapping.Scenario].Started = true;
- _options.Scenarios[targetMapping.Scenario].Finished = targetMapping.NextState == null;
+ UpdateScenarioState(targetMapping);
}
}
catch (Exception ex)
@@ -157,6 +155,25 @@ namespace WireMock.Owin
await CompletedTask;
}
+ private void UpdateScenarioState(IMapping mapping)
+ {
+ var scenario = _options.Scenarios[mapping.Scenario];
+
+ // Increase the number of times this state has been executed
+ scenario.Counter++;
+
+ // Only if the number of times this state is executed equals the required StateTimes, proceed to next state and reset the counter to 0
+ if (scenario.Counter == (mapping.StateTimes ?? 1))
+ {
+ scenario.NextState = mapping.NextState;
+ scenario.Counter = 0;
+ }
+
+ // Else just update Started and Finished
+ scenario.Started = true;
+ scenario.Finished = mapping.NextState == null;
+ }
+
private void LogRequest(LogEntry entry, bool addRequest)
{
_options.Logger.DebugRequestResponse(LogEntryMapper.Map(entry), entry.RequestMessage.Path.StartsWith("/__admin/"));
diff --git a/src/WireMock.Net/ScenarioState.cs b/src/WireMock.Net/ScenarioState.cs
index 8a28da93..d4671471 100644
--- a/src/WireMock.Net/ScenarioState.cs
+++ b/src/WireMock.Net/ScenarioState.cs
@@ -24,5 +24,10 @@
/// Gets or sets a value indicating whether this is finished.
///
public bool Finished { get; set; }
+
+ ///
+ /// Gets or sets the state counter.
+ ///
+ public int Counter { get; set; }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Server/IRespondWithAProvider.cs b/src/WireMock.Net/Server/IRespondWithAProvider.cs
index 18c09520..50ea594d 100644
--- a/src/WireMock.Net/Server/IRespondWithAProvider.cs
+++ b/src/WireMock.Net/Server/IRespondWithAProvider.cs
@@ -86,14 +86,16 @@ namespace WireMock.Server
/// Once this mapping is executed the state will be changed to specified one.
///
/// Any object which identifies the new state
+ /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1.
/// The .
- IRespondWithAProvider WillSetStateTo(string state);
+ IRespondWithAProvider WillSetStateTo(string state, int? times = 1);
///
/// Once this mapping is executed the state will be changed to specified one.
///
/// Any object which identifies the new state
+ /// The number of times this match should be matched before the state will be changed to the specified one. Default value is 1.
/// The .
- IRespondWithAProvider WillSetStateTo(int state);
+ IRespondWithAProvider WillSetStateTo(int state, int? times = 1);
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Server/RespondWithAProvider.cs b/src/WireMock.Net/Server/RespondWithAProvider.cs
index 6b18baaa..b2c303d6 100644
--- a/src/WireMock.Net/Server/RespondWithAProvider.cs
+++ b/src/WireMock.Net/Server/RespondWithAProvider.cs
@@ -18,6 +18,7 @@ namespace WireMock.Server
private string _executionConditionState;
private string _nextState;
private string _scenario;
+ private int _timesInSameState = 1;
private readonly RegistrationCallback _registrationCallback;
private readonly IRequestMatcher _requestMatcher;
private readonly IWireMockServerSettings _settings;
@@ -46,7 +47,7 @@ namespace WireMock.Server
/// The provider.
public void RespondWith(IResponseProvider provider)
{
- _registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState), _saveToFile);
+ _registrationCallback(new Mapping(Guid, _title, _path, _settings, _requestMatcher, provider, _priority, _scenario, _executionConditionState, _nextState, _timesInSameState), _saveToFile);
}
///
@@ -120,8 +121,8 @@ namespace WireMock.Server
return WhenStateIs(state.ToString());
}
- ///
- public IRespondWithAProvider WillSetStateTo(string state)
+ ///
+ public IRespondWithAProvider WillSetStateTo(string state, int? times = 1)
{
if (string.IsNullOrEmpty(_scenario))
{
@@ -129,14 +130,15 @@ namespace WireMock.Server
}
_nextState = state;
+ _timesInSameState = times ?? 1;
return this;
}
- ///
- public IRespondWithAProvider WillSetStateTo(int state)
+ ///
+ public IRespondWithAProvider WillSetStateTo(int state, int? times = 1)
{
- return WillSetStateTo(state.ToString());
+ return WillSetStateTo(state.ToString(), times);
}
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs
index d673f02d..85d832d5 100644
--- a/src/WireMock.Net/Server/WireMockServer.Admin.cs
+++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs
@@ -331,7 +331,7 @@ namespace WireMock.Server
var response = Response.Create(responseMessage);
- return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null);
+ return new Mapping(Guid.NewGuid(), string.Empty, null, _settings, request, response, 0, null, null, null, null);
}
#endregion
@@ -706,7 +706,8 @@ namespace WireMock.Server
Name = s.Name,
NextState = s.NextState,
Started = s.Started,
- Finished = s.Finished
+ Finished = s.Finished,
+ Counter = s.Counter
});
return ToJson(scenariosStates, true);
diff --git a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
index c5de40c9..31987032 100644
--- a/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
+++ b/test/WireMock.Net.Tests/Serialization/MappingConverterTests.cs
@@ -25,7 +25,7 @@ namespace WireMock.Net.Tests.Serialization
// Assign
var request = Request.Create();
var response = Response.Create();
- var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null);
+ var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
@@ -44,7 +44,7 @@ namespace WireMock.Net.Tests.Serialization
// Assign
var request = Request.Create();
var response = Response.Create().WithBodyAsJson(new { x = "x" }).WithTransformer();
- var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null);
+ var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 42, null, null, null, null);
// Act
var model = _sut.ToMappingModel(mapping);
diff --git a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs
index 4f5c4e6f..ad23eb5e 100644
--- a/test/WireMock.Net.Tests/StatefulBehaviorTests.cs
+++ b/test/WireMock.Net.Tests/StatefulBehaviorTests.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
+using FluentAssertions;
using NFluent;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -61,6 +62,89 @@ namespace WireMock.Net.Tests
Check.That(responseWithState).Equals("Test state msg");
}
+ [Fact]
+ public async Task Scenarios_With_Same_Path_Should_Use_Times_When_Moving_To_Next_State()
+ {
+ // given
+ const int times = 2;
+ string path = $"/foo_{Guid.NewGuid()}";
+ string body1 = "Scenario S1, No State, Setting State T2";
+ string body2 = "Scenario S1, State T2, End";
+ var server = WireMockServer.Start();
+
+ server
+ .Given(Request.Create().WithPath(path).UsingGet())
+ .InScenario(1)
+ .WillSetStateTo(2, times)
+ .RespondWith(Response.Create().WithBody(body1));
+
+ server
+ .Given(Request.Create().WithPath(path).UsingGet())
+ .InScenario(1)
+ .WhenStateIs(2)
+ .RespondWith(Response.Create().WithBody(body2));
+
+ // when
+ var client = new HttpClient();
+ var responseScenario1 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path);
+ var responseScenario2 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path);
+ var responseWithState = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path);
+
+ // then
+ responseScenario1.Should().Be(body1);
+ responseScenario2.Should().Be(body1);
+ responseWithState.Should().Be(body2);
+ }
+
+ [Fact]
+ public async Task Scenarios_With_Different_Paths_Should_Use_Times_When_Moving_To_Next_State()
+ {
+ // given
+ const int times = 2;
+ string path1 = $"/a_{Guid.NewGuid()}";
+ string path2 = $"/b_{Guid.NewGuid()}";
+ string path3 = $"/c_{Guid.NewGuid()}";
+ string body1 = "Scenario S1, No State, Setting State T2";
+ string body2 = "Scenario S1, State T2, Setting State T3";
+ string body3 = "Scenario S1, State T3, End";
+
+ var server = WireMockServer.Start();
+
+ server
+ .Given(Request.Create().WithPath(path1).UsingGet())
+ .InScenario("S1")
+ .WillSetStateTo("T2", times)
+ .RespondWith(Response.Create().WithBody(body1));
+
+ server
+ .Given(Request.Create().WithPath(path2).UsingGet())
+ .InScenario("S1")
+ .WhenStateIs("T2")
+ .WillSetStateTo("T3", times)
+ .RespondWith(Response.Create().WithBody(body2));
+
+ server
+ .Given(Request.Create().WithPath(path3).UsingGet())
+ .InScenario("S1")
+ .WhenStateIs("T3")
+ .RespondWith(Response.Create().WithBody(body3));
+
+ // when
+ var client = new HttpClient();
+ var t1a = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path1);
+ var t1b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path1);
+ var t2a = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2);
+ var t2b = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path2);
+ var t3 = await client.GetStringAsync("http://localhost:" + server.Ports[0] + path3);
+
+ // then
+ t1a.Should().Be(body1);
+ t1b.Should().Be(body1);
+ t2a.Should().Be(body2);
+ t2b.Should().Be(body2);
+ t3.Should().Be(body3);
+ }
+
[Fact]
public async Task Scenarios_Should_Respect_Int_Valued_Scenarios_and_States()
{